Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.getType() == Token.GETPROP &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.getType() == Token.GETPROP &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, level, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.getType() == Token.VAR) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> *
* Note: jsscan.c uses C isspace() (which allows
* \v, I think.) note that code in getChar() implicitly accepts
* '\r' == \u000D as well.
*/
static boolean isJSSpace(int c)
{
if (c <= 127) {
return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB;
} else {
return c == 0xA0
|| Character.getType((char)c) == Character.SPACE_SEPARATOR;
}
}
private static boolean isJSFormatChar(int c)
{
return c > 127 && Character.getType((char)c) == Character.FORMAT;
}
/**
* Gets the accumulated {@link JSDocInfo} and resets it.
* Obsolete
*/
JSDocInfo getAndResetJSDocInfo() {
return null;
}
/**
* Returns any {@link JSDocInfo} with a fileoverview tag that showed up.
* Obsolete
*/
JSDocInfo getFileOverviewJSDocInfo() {
return null;
}
/**
* Returns whether any {@link JSDocInfo} was accumulated.
* Obsolete
*/
boolean isPopulated() {
return false;
}
/**
* Parser calls the method when it gets / or /= in literal context.
*/
void readRegExp(int startToken)
throws IOException
{
stringBufferTop = 0;
if (startToken == Token.ASSIGN_DIV) {
// Miss-scanned /=
addToString('=');
} else {
if (startToken != Token.DIV) Kit.codeBug();
}
boolean inCharSet = false; // true if inside a '['..']' pair
int c;
while ((c = getChar()) != '/' || inCharSet) {
if (c == '\n' || c == EOF_CHAR) {
ungetChar(c);
throw parser.reportError("msg.unterminated.re.lit");
}
if (c == '\\') {
addToString(c);
c = getChar();
} else if (c == '[') {
inCharSet = true;
} else if (c == ']') {
inCharSet = false;
}
addToString(c);
}
int reEnd = stringBufferTop;
while (true) {
if (matchChar('g'))
addToString('g');
else if (matchChar('i'))
addToString('i');
else if (matchChar('m'))
addToString('m');
else
break;
}
if (isAlpha(peekChar())) {
throw parser.reportError("msg.invalid.re.flag");
}
this.string = new String(stringBuffer, 0, reEnd);
this.regExpFlags = new String(stringBuffer, reEnd,
stringBufferTop - reEnd);
}
boolean isXMLAttribute()
{
return xmlIsAttribute;
}
int getFirstXMLToken() throws IOException
{
xmlOpenTagsCount = 0;
xmlIsAttribute = false;
xmlIsTagContent = false;
ungetChar('<');
return getNextXMLToken();
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatementContext) {
Preconditions.checkState(n.getType() == Token.BLOCK);
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
// Don't break before finally
return !NodeUtil.hasFinally(getTryForCatch(parent));
case Token.IF:
// Don't break before else
return n == parent.getLastChild();
}
}
return true;
}
}
static class CompactCodePrinter
extends MappedCodePrinter {
// The CompactCodePrinter tries to emit just enough newlines to stop there
// being lines longer than the threshold. Since the output is going to be
// gzipped, it makes sense to try to make the newlines appear in similar
// contexts so that GZIP can encode them for 'free'.
//
// This version tries to break the lines at 'preferred' places, which are
// between the top-level forms. This works because top level forms tend to
// be more uniform than arbitary legal contexts. Better compression would
// probably require explicit modelling of the gzip algorithm.
private final boolean lineBreak;
private int lineStartPosition = 0;
private int preferredBreakPosition = 0;
/**
* @param lineBreak break the lines a bit more aggressively
* @param lineLengthThreshold The length of a line after which we force
* a newline when possible.
* @param createSrcMap Whether to gather source position
* mapping information when printing.
* @param sourceMapDetailLevel A filter to control which nodes get mapped into
* the source map.
*/
private CompactCodePrinter(boolean lineBreak, int lineLengthThreshold,
boolean createSrcMap, SourceMap.DetailLevel sourceMapDetailLevel) {
super(lineLengthThreshold, createSrcMap, sourceMapDetailLevel);
this.lineBreak = lineBreak;
}
/**
* Appends a string to the code, keeping track of the current line length.
*/
@Override
void append(String str) {
code.append(str);
lineLength += str.length();
}
/**
* Adds a newline to the code, resetting the line length.
*/
@Override
void startNewLine() {
if (lineLength > 0) {
code.append('\n');
lineLength = 0;
lineIndex++;
lineStartPosition = code.length();
}
}
@Override
void maybeLineBreak() {
if (lineBreak) {
if (sawFunction) {
startNewLine();
sawFunction = false;
}
}
// Since we are at a legal line break, can we upgrade the
// preferred break position? We prefer to break after a
//
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final
Function<TypePair, TypePair> INEQ =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) {
typeOfNode = left;
stringNode = right;
} else if (right.getType() == Token.TYPEOF &&
left.getType() == Token.STRING) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
}
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, INEQ);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().getType() == Token.STRING) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
return caseEquality(condition.getFirstChild(), condition.getLastChild(),
blindScope, merging);
}
private FlowScope caseEquality(Node left, Node right, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
// left type
JSType leftType = getTypeIfRefinable(left, blindScope);
boolean leftIsRefineable;
if (leftType != null) {
leftIsRefineable = true;
} else {
leftIsRefineable = false;
leftType = left.getJSType();
}
// right type
JSType rightType = getTypeIfRefinable(right, blindScope);
boolean rightIsRefineable;
if (rightType != null) {
rightIsRefineable = true;
} else {
rightIsRefineable = false;
rightType = right.getJSType();
}
// merged types
TypePair merged = merging.apply(new TypePair(leftType, rightType));
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> <li>A</li>
* <li>213</li>
* <li>FOO_BAR</li>
* </ul>
*/
@Override
public boolean isValidEnumKey(String key) {
return ENUM_KEY_PATTERN.matcher(key).matches();
}
/**
* {@inheritDoc}
*
* <p>In Google code, parameter names beginning with {@code opt_} are
* treated as optional arguments.
*/
@Override
public boolean isOptionalParameter(Node parameter) {
return parameter.getString().startsWith(OPTIONAL_ARG_PREFIX);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return VAR_ARGS_NAME.equals(parameter.getString());
}
/**
* {@inheritDoc}
*
* <p>In Google code, any global name starting with an underscore is
* considered exported.
*/
@Override
public boolean isExported(String name, boolean local) {
return !local && name.startsWith("_");
}
/**
* {@inheritDoc}
*
* <p>In Google code, private names end with an underscore, and exported
* names are never considered private (see {@link #isExported}).
*/
@Override
public boolean isPrivate(String name) {
return name.endsWith("_") && !isExported(name);
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno());
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
extends NodeTraversal.AbstractPostOrderCallback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
case Token.NAME:
case Token.STRING:
annotateConstants(n, parent);
break;
case Token.OBJECTLIT:
visitObjectLiteral(n);
break;
}
}
private void visitObjectLiteral(Node objlit) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
visitObjectLiteralKey(objlit, key, value);
}
}
/**
* Prepare the object literal keys.
*/
private void visitObjectLiteralKey(Node objlit, Node key, Node value) {
normalizeObjectLitJsDocs(objlit, key, value);
annotateObjLitConstants(objlit, key, value);
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.getType() == Token.NAME &&
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> "eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.getType() == Token.ASSIGN) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLitJsDocs(Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
if (key.getJSDocInfo() != null &&
value.getType() == Token.FUNCTION) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Mark names and properties that are constants by convention.
*/
private void annotateConstants(Node n, Node parent) {
Preconditions.checkState(
n.getType() == Token.NAME || n.getType() == Token.STRING);
// There are only two cases where a string token
// may be a variable reference: The right side of a GETPROP
// or an OBJECTLIT key.
if (n.getType() != Token.STRING
|| parent.getType() == Token.GETPROP) {
if (NodeUtil.isConstantByConvention(convention, n, parent)) {
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
/**
* Mark objlit names that are constants by convention.
*/
private void annotateObjLitConstants(Node objlit, Node key, Node value) {
if (key.getType() == Token.NAME || key.getType() == Token.STRING) {
String name = key.getString();
if (convention.isConstantKey(name)) {
key.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(arg
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>, JSType ownerType) {
if (!overridingType.canAssignTo(hiddenType)) {
registerMismatch(overridingType, hiddenType);
if (shouldReport) {
compiler.report(
t.makeError(n, HIDDEN_PROPERTY_MISMATCH,
propertyName, ownerType.toString(),
hiddenType.toString(), overridingType.toString()));
}
}
}
/**
* Expect that the first type is the direct superclass of the second type.
*
* @param t The node traversal.
* @param n The node where warnings should point to.
* @param superObject The expected super instance type.
* @param subObject The sub instance type.
*/
void expectSuperType(NodeTraversal t, Node n, ObjectType superObject,
ObjectType subObject) {
FunctionType subCtor = subObject.getConstructor();
ObjectType declaredSuper =
subObject.getImplicitPrototype().getImplicitPrototype();
if (!declaredSuper.equals(superObject)) {
if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) {
if (shouldReport) {
compiler.report(
t.makeError(n, MISSING_EXTENDS_TAG_WARNING,
subObject.toString()));
}
registerMismatch(superObject, declaredSuper);
} else {
mismatch(t.getSourceName(), n,
"mismatch in declaration of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* should be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
if (shouldReport) {
compiler.report(
t.makeError(n, INVALID_CAST,
castType.toString(), type.toString()));
}
registerMismatch(type, castType);
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
*/
void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var,
String variableName, JSType newType) {
boolean allowDupe = false;
if (n.getType() == Token.GETPROP) {
JSDocInfo info = n.getJSDocInfo();
if (info == null)
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type.
if (var.input == null) {
n.setJSType(varType);
if (parent.getType() == Token.VAR) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.getType() == Token.FUNCTION);
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().getType() == Token.EXPR_RESULT) ||
!newType.equals(varType)) {
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
}
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented.
*/
void expectAllInterfacePropertiesImplemented(FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
if (!instance.hasProperty(prop)) {
Node source = type.getSource();
Preconditions.checkNotNull(source);
String sourceName = (String) source.getProp(Node.SOURCENAME_PROP);
sourceName = sourceName == null ? "" : sourceName;
if (shouldReport) {
compiler.report(JSError.make(sourceName, source,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implemented.toString(), instance.toString()));
}
registerMismatch(instance, implemented);
}
}
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
}
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSTypeNative required) {
mismatch(t, n, msg, found,
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
name.declaration.sourceName,
name.declaration.node,
INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else if (name.refs != null) {
for (Ref ref : name.refs) {
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.getType() == Token.VAR && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
if (name.declaration != null) {
allRefInfo.put(name.declaration.node,
new RefInfo(name.declaration, name));
}
if (name.refs != null) {
for (Ref ref : name.refs) {
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
}
/**
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> * Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
* We traverse all nodes of the parse tree.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
updateAssignAllowedStack(n, true);
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
RefInfo refInfo = allRefInfo.get(n);
if (refInfo != null) {
Ref ref = refInfo.ref;
Name name = refInfo.name;
String fullName = name.fullName();
switch (ref.type) {
case SET_FROM_GLOBAL:
case SET_FROM_LOCAL:
Node valParent = getValueParent(ref);
Node val = valParent.getLastChild();
if (valParent.getType() == Token.ASSIGN && name.isSimpleName() &&
name.declaration == ref) {
// For defines, it's an error if a simple name is assigned
// before it's declared
compiler.report(
t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName));
} else if (processDefineAssignment(t, fullName, val, valParent)) {
// remove the assignment so that the variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.getType() == Token.ASSIGN) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.getType() == Token.NAME);
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.getType() == Token.CALL) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define was already initialized, and this is an unsafe
// re-assignment.
compiler.report(
t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR,
name, info.getReasonWhyNotAssignable()));
}
}
return false;
}
/**
* Gets the parent node of the value for any assignment to a Name.
* For example, in the assignment
* {@code var x = 3
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>;}
* the parent would be the NAME node.
*/
private static Node getValueParent(Ref ref) {
// there are two types of declarations: VARs and ASSIGNs
return ref.node.getParent() != null &&
ref.node.getParent().getType() == Token.VAR ?
ref.node : ref.node.getParent();
}
/**
* Records the fact that because of the current node in the node traversal,
* the define can't ever be assigned again.
*
* @param info Represents the define variable.
* @param t The current traversal.
*/
private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) {
info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE,
t.getLineNumber(), t.getSourceName()));
}
/**
* A simple data structure for associating a Ref with the name
* that it references.
*/
private static class RefInfo {
final Ref ref;
final Name name;
RefInfo(Ref ref, Name name) {
this.ref = ref;
this.name = name;
}
}
}
/**
* A simple class for storing information about a define.
* Gathers the initial value, the last assigned value, and whether
* the define can be safely assigned a new value.
*/
private static final class DefineInfo {
public final Node initialValueParent;
public final Node initialValue;
private Node lastValue;
private boolean isAssignable;
private String reasonNotAssignable;
/**
* Initializes a define.
*/
public DefineInfo(Node initialValue, Node initialValueParent) {
this.initialValueParent = initialValueParent;
this.initialValue = initialValue;
lastValue = initialValue;
isAssignable = true;
}
/**
* Records the fact that this define can't be assigned a value anymore.
*
* @param reason A message describing the reason why it can't be assigned.
*/
public void setNotAssignable(String reason) {
isAssignable = false;
reasonNotAssignable = reason;
}
/**
* Gets the reason why a define is not assignable.
*/
public String getReasonWhyNotAssignable() {
return reasonNotAssignable;
}
/**
* Records an assigned value.
*
* @return False if there was an error.
*/
public boolean recordAssignment(Node value) {
lastValue = value;
return isAssignable;
}
/**
* Gets the last assigned value.
*/
public Node getLastValue() {
return lastValue;
}
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>Traversal nodeTraversal, Node n, Node parent);
/**
* <p>Visits a node in post order (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to visit all nodes in post order.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (!sourceName.isEmpty()) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
if (n == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.is
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>Global());
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.getType() == Token.FUNCTION) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may be not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(sourceName);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.io.IOException;
/**
* Collects information mapping the generated (compiled) source back to
* its original source for debugging purposes.
*
* @see CodeConsumer
* @see CodeGenerator
* @see CodePrinter
*
*/
public interface SourceMap {
enum Format {
LEGACY {
@Override SourceMap getInstance() {
return new SourceMapLegacy();
}
},
EXPERIMENTIAL {
@Override SourceMap getInstance() {
return new SourceMap2();
}
};
abstract SourceMap getInstance();
}
/**
* Source maps can be very large different levels of detail can be specified.
*/
public enum DetailLevel implements Predicate<Node> {
// ALL is best when the fullest details are needed for debugging or for
// code-origin analysis.
ALL {
@Override public boolean apply(Node node) {
return true;
}
},
// SYMBOLS is intended to be used for stack trace deobfuscation when full
// detail is not needed.
SYMBOLS {
@Override public boolean apply(Node node) {
return NodeUtil.isCall(node)
|| NodeUtil.isNew(node)
|| NodeUtil.isFunction(node)
|| NodeUtil.isName(node)
|| NodeUtil.isGet(node)
|| (NodeUtil.isString(node) &&
(node.getParent().getType() == Token.OBJECTLIT ||
NodeUtil.isGet(node.getParent())));
}
};
}
/**
* Appends the source map to the given buffer.
*
* @param out The stream to which the map will be appended.
* @param name The name of the generated source file that this source map
* represents.
*/
void appendTo(Appendable out, String name) throws IOException;
/**
* Resets the source map for reuse. A reset needs to be called between
* each generated output file.
*/
void reset();
/**
* Adds a mapping for the given node. Mappings must be added in order.
*
* @param node The node that the new mapping represents.
* @param startPosition The position on the starting line
* @param endPosition The position on the ending line.
*/
void addMapping(Node node, Position startPosition, Position endPosition);
/**
* Sets the prefix used for wrapping the generated source file before
* it is written. This ensures that the source map is adjusted for the
* change in character offsets.
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(NodeUtil.isFunction(parent));
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isFunctionExpression(parent)) {
t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.getType() == Token.VAR ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
parent.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
if (index < references.size() && index > 0) {
Reference maybeDecl = references.get(index-1);
if (maybeDecl.isVarDeclaration()) {
Preconditions.checkState(!maybeDecl.isInitializingDeclaration());
Reference maybeInit = references.get(index);
if (maybeInit.isSimpleAssignmentToName()) {
return true;
}
}
}
return false;
}
/**
* @return The reference that provides the value for the variable at the
* time of the first read, if known, otherwise null.
*
* This is either the variable declaration ("var a = ...") or first
* reference following the declaration if it is an assignment.
*/
Reference getInitializingReference() {
if (isInitializingDeclarationAt(0)) {
return references.get(0);
} else if (isInitializingAssignmentAt(1)) {
return references.get(1);
}
return null;
}
/**
* Constants are allowed to be defined after their first use.
*/
Reference getInitializingReferenceForConstants() {
int size = references.size();
for (int i = 0; i < size; i++) {
if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) {
return references.get(i);
}
}
return null;
}
/**
* @return Whether the variable is only assigned a value once for its
* lifetime.
*/
boolean isAssignedOnceInLifetime() {
Reference ref = getOneAndOnlyAssignment();
if (ref == null) {
return false;
}
// Make sure this assignment is not in a loop.
for (BasicBlock block = ref.getBasicBlock();
block != null; block = block.getParent()) {
if (block.isFunction) {
break;
} else if (block.isLoop) {
return false;
}
}
return true;
}
/**
* @return The one and only assignment. Returns if there are 0 or 2+
* assignments.
*/
private Reference getOneAndOnlyAssignment() {
Reference assignment = null;
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
if (assignment == null) {
assignment = ref;
} else {
return null;
}
}
}
return assignment;
}
/**
* @return Whether the variable is never assigned a value.
*/
boolean isNeverAssigned() {
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
* Represents a single declaration or reference to a variable.
*/
static final class Reference {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR,
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final Node parent;
private final Node grandparent;
private final BasicBlock basicBlock;
private final Scope scope;
private final String sourceName;
Reference(Node nameNode, Node parent, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, parent, parent.getParent(), basicBlock, t.getScope(),
t.getSourceName());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(), func, func.getParent(),
basicBlock, t.getScope(), t.getSourceName());
}
private Reference(Node nameNode, Node parent, Node grandparent,
BasicBlock basicBlock, Scope scope, String sourceName) {
this.nameNode = nameNode;
this.parent = parent;
this.grandparent = grandparent;
this.basicBlock = basicBlock;
this.scope = scope;
this.sourceName = sourceName;
}
boolean isDeclaration() {
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.getType() == Token.LP &&
grandparent.getType() == Token.FUNCTION;
}
boolean isVarDeclaration() {
return parent.getType() == Token.VAR;
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(parent);
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
(parent.getType() != Token.VAR || nameNode.getFirstChild() != null);
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
return (parent.getType() == Token.FUNCTION)
? parent : NodeUtil.getAssignedValue(getNameNode());
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return parent;
}
Node getNameNode() {
return nameNode;
}
Node getGrandparent() {
return grandparent;
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.getType() == Token.VAR) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
return parent.getType() == Token.ASSIGN
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
Scope getScope() {
return scope;
}
public String getSourceName() {
return sourceName;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.getType() == Token.FUNCTION;
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
return currentBlock == this;
}
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final TypeCheck outerThis = this;
final Scope outerScope = t.getScope();
final FunctionType functionType = (FunctionType) n.getJSType();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> }
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.LP:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.REF_SPECIAL:
ensureTyped(t, n);
break;
case Token.GET_REF:
ensureTyped(t, n, getJSType(n.getFirstChild()));
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, NUMBER_TYPE);
} else {
typeable = false;
}
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.STRING:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, STRING_TYPE);
} else {
typeable = false;
}
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
type
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>able = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result =
leftTypeRestricted.testForEquality(rightTypeRestricted);
if (result != TernaryValue.UNKNOWN) {
if (n.getType() == Token.NE) {
result = result.not();
}
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(),
rightType.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
if (rightType.isNumber()) {
validator.expectNumber(
t, n, leftType, "left side of numeric comparison");
} else if (leftType.isNumber())
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.getType() == Token.OBJECTLIT)
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
if (lvalue.getType() == Token.GETPROP) {
Node object =
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
String property = lvalue.getLastChild().getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.getType() == Token.GETPROP) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
// /** @type ... */object.name = ...;
if (info != null && info.hasType()) {
visitAnnotatedAssignGetprop(t, assign,
info.getType().evaluate(t.getScope(), typeRegistry), object,
property, rvalue);
return;
}
// /** @enum ... */object.name = ...;
if (info != null && info.hasEnumParameterType()) {
checkEnumInitializer(
t, rvalue, info.getEnumParameterType().evaluate(
t.getScope(), typeRegistry));
return;
}
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) objectJsType;
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
}
} else {
// TODO(user): might want to flag that
}
return;
}
// object.prototype.property = ...;
if (object.getType() == Token.GETPROP) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = object2.getJSType();
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, getJSType(rvalue));
}
} else {
// TODO(user): might want to flag that
}
return;
}
}
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
type.getPropertyType(property), object, property);
}
return;
}
} else if (lvalue.getType() == Token.NAME) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getString());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
}
}
// fall through case
JSType leftType =
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> getJSType(lvalue);
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* Returns true if any type in the chain has an implictCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclaredPropertyInheritance(
NodeTraversal t, Node n, FunctionType ctorType, String propertyName,
JSDocInfo info, JSType propertyType) {
// TODO(user): We're not 100% confident that type-checking works,
// so we return quietly if the unknown type is a superclass of this type.
// Remove this check as we become more confident. We should flag a warning
// when the unknown type is on the inheritance chain, as it is likely
// because of a programmer error.
if (ctorType.hasUnknownSupertype()) {
return;
}
FunctionType superClass = ctorType.getSuperClassConstructor();
boolean superClassHasProperty = superClass != null &&
superClass.getPrototype().hasProperty(propertyName);
boolean declaredOverride = info != null && info.isOverride();
boolean foundInterfaceProperty = false;
if (ctorType.isConstructor()) {
for (JSType implementedInterface : ctorType.getImplementedInterfaces()) {
if (implementedInterface.isUnknownType()) {
continue;
}
FunctionType interfaceType =
implementedInterface.toObjectType().getConstructor();
boolean interfaceHasProperty =
interfaceType.getPrototype().hasProperty(propertyName);
foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty;
if (reportMissingOverride.isOn() && !declaredOverride &&
interfaceHasProperty) {
// @override not present, but the property does override an interface
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_INTERFACE_PROPERTY, propertyName,
interfaceType.getTopMostDefiningType(propertyName).toString()));
}
if (!declaredOverride) {
continue;
}
// @override is present and we have to check that it is ok
if (interfaceHasProperty) {
JSType interfacePropType =
interfaceType.getPrototype().getPropertyType(propertyName);
if (!propertyType.canAssignTo(interfacePropType)) {
compiler.report(t.makeError(n,
HIDDEN_INTERFACE_PROPERTY_MISMATCH, propertyName,
interfaceType.getTopMostDefiningType(propertyName).toString(),
interfacePropType.toString(), propertyType.toString()));
}
}
}
}
if (!declaredOverride && !superClassHasProperty
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>) {
// nothing to do here, it's just a plain new property
return;
}
JSType topInstanceType = superClassHasProperty ?
superClass.getTopMostDefiningType(propertyName) : null;
if (reportMissingOverride.isOn() && ctorType.isConstructor() &&
!declaredOverride && superClassHasProperty) {
// @override not present, but the property does override a superclass
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_SUPERCLASS_PROPERTY, propertyName,
topInstanceType.toString()));
}
if (!declaredOverride) {
// there's no @override to check
return;
}
// @override is present and we have to check that it is ok
if (superClassHasProperty) {
// there is a superclass implementation
JSType superClassPropType =
superClass.getPrototype().getPropertyType(propertyName);
if (!propertyType.canAssignTo(superClassPropType)) {
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superClassPropType.toString(), propertyType.toString()));
}
} else if (!foundInterfaceProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isOrdinaryFunction() &&
!(rvalue.isQualifiedName() &&
rvalue.getQualifiedName().equals(abstractMethodName))) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().getType() == Token.FUNCTION
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JSType type, Node object, String property, Node rvalue) {
// verifying that the rvalue has the correct type
validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type,
object, property);
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>();
if (objectType != null) {
JSType propType = getJSType(n);
if ((!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) &&
propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
} else {
// TODO(nicksantos): might want to flag the access on a non object when
// it's impossible to get a property from this type.
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().getType() == Token.OR &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
if (info != null && info.hasEnumParameterType()) {
// var.getType() can never be null, this would indicate a bug in the
// scope creation logic.
checkEnumInitializer(
t, value,
info.getEnumParameterType().evaluate(t.getScope(), typeRegistry));
} else if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
FunctionType type = getFunctionType(constructor);
if (type != null && type.isConstructor()) {
visitParameterList(t, n, type);
ensureTyped(t, n, type.getInstanceType());
} else {
// TODO(user): add support for namespaced objects.
if (constructor.getType() != Token.GETPROP) {
// TODO(user): make the constructor node have lineno/charno
// and use constructor for a more precise error indication.
// It seems that GETPROP nodes are missing this information.
Node line;
if (constructor.getLineno() < 0 || constructor.getCharno() < 0) {
line = n;
} else {
line = constructor;
}
report(t, line, NOT_A_CONSTRUCTOR);
}
ensureTyped(t, n);
}
}
/**
* Visits a {@link Token#FUNCTION} node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitFunction(NodeTraversal t, Node n) {
JSDocInfo info = n.getJSDocInfo();
FunctionType functionType = (FunctionType) n.getJSType();
String functionPrivateName = n.getFirstChild().getString();
if (functionType.isInterface() || functionType.isConstructor()) {
FunctionType baseConstructor = functionType.
getPrototype().getImplicitPrototype().getConstructor();
if (baseConstructor != null &&
baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) &&
(baseConstructor.isConstructor() && functionType.isInterface() ||
baseConstructor.isInterface() && functionType.isConstructor())) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName));
}
for (JSType baseInterface : functionType.getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>),
maxArgs != Integer.MAX_VALUE ?
" and no more than " + maxArgs + " argument(s)" : "");
}
}
/**
* Visits a RETURN node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitReturn(NodeTraversal t, Node n) {
Node function = t.getEnclosingFunction();
// This is a misplaced return, but the real JS will fail to compile,
// so let it go.
if (function == null) {
return;
}
JSType jsType = getJSType(function);
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
JSType returnType = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
/**
* Gets the type of the node or {@code null} if the node's type is not a
* function.
*/
private FunctionType getFunctionType(Node n) {
JSType type = getJSType(n).restrictByNotNullOrUndefined();
if (type.isUnknownType()) {
return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE);
} else if (type instanceof FunctionType) {
return (FunctionType) type;
} else {
return null;
}
}
// TODO(nicksantos): TypeCheck should never be attaching types to nodes.
// All types should be attached by TypeInference. This is not true today
// for legacy reasons. There are a number of places where TypeInference
// doesn't attach a type, as a signal to TypeCheck that it needs to check
// that node's type.
/**
* Ensure that the given node has a type. If it does not have one,
* attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(n.getType() != Token.FUNCTION ||
type instanceof FunctionType ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
}
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>.javascript.jscomp.mozilla.rhino.ast.NumberLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectProperty;
import com.google.javascript.jscomp.mozilla.rhino.ast.ParenthesizedExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.PropertyGet;
import com.google.javascript.jscomp.mozilla.rhino.ast.RegExpLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ReturnStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.Scope;
import com.google.javascript.jscomp.mozilla.rhino.ast.StringLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchCase;
import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.ThrowStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.TryStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.UnaryExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.VariableDeclaration;
import com.google.javascript.jscomp.mozilla.rhino.ast.VariableInitializer;
import com.google.javascript.jscomp.mozilla.rhino.ast.WhileLoop;
import com.google.javascript.jscomp.mozilla.rhino.ast.WithStatement;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* IRFactory transforms the new AST to the old AST.
*
*/
public class IRFactory {
private final String sourceString;
private final String sourceName;
private final Config config;
private final ErrorReporter errorReporter;
private final TransformDispatcher transformDispatcher;
// non-static for thread safety
private final Set<String> ALLOWED_DIRECTIVES = Sets.newHashSet("use strict");
// @license text gets appended onto the fileLevelJsDocBuilder as found,
// and stored in JSDocInfo for placeholder node.
Node rootNodeJsDocHolder = new Node(Token.SCRIPT);
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder =
rootNodeJsDocHolder.getJsDocBuilderForNode();
JSDocInfo fileOverviewInfo = null;
// Use a template node for properties set on all nodes to minimize the
// memory footprint associated with these.
private Node templateNode;
// TODO(johnlenz): Consider creating a template pool for ORIGINALNAME_PROP.
private IRFactory(String sourceString,
String sourceName,
Config config,
ErrorReporter errorReporter) {
this.sourceString = sourceString;
this.sourceName = sourceName;
this.config = config;
this.errorReporter = errorReporter;
this.transformDispatcher = new TransformDispatcher();
// The template node properties are applied to all nodes in this transform.
this.templateNode = createTemplateNode();
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> }
// Create a template node to use as a source of common attributes, this allows
// the prop structure to be shared among all the node from this source file.
// This reduces the cost of these properties to O(nodes) to O(files).
private Node createTemplateNode() {
// The Node type choice is arbitrary.
Node templateNode = new Node(Token.SCRIPT);
templateNode.putProp(Node.SOURCENAME_PROP, sourceName);
return templateNode;
}
public static Node transformTree(AstRoot node,
String sourceString,
Config config,
ErrorReporter errorReporter) {
IRFactory irFactory = new IRFactory(sourceString, node.getSourceName(),
config, errorReporter);
Node irNode = irFactory.transform(node);
if (node.getComments() != null) {
for (Comment comment : node.getComments()) {
if (comment.getCommentType() == JSDOC && !comment.isParsed()) {
irFactory.handlePossibleFileOverviewJsDoc(comment);
}
}
}
irFactory.setFileOverviewJsDoc(irNode);
return irNode;
}
private void setFileOverviewJsDoc(Node irNode) {
// Only after we've seen all @fileoverview entries, attach the
// last one to the root node, and copy the found license strings
// to that node.
irNode.setJSDocInfo(rootNodeJsDocHolder.getJSDocInfo());
if (fileOverviewInfo != null) {
if ((irNode.getJSDocInfo() != null) &&
(irNode.getJSDocInfo().getLicense() != null)) {
fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
}
}
private Node transformBlock(AstNode node) {
Node irNode = transform(node);
if (irNode.getType() != Token.BLOCK) {
if (irNode.getType() == Token.EMPTY) {
irNode.setType(Token.BLOCK);
irNode.setWasEmptyNode(true);
} else {
Node newBlock = newNode(Token.BLOCK, irNode);
newBlock.setLineno(irNode.getLineno());
newBlock.setCharno(irNode.getCharno());
irNode = newBlock;
}
}
return irNode;
}
/**
* @return true if the jsDocParser represents a fileoverview.
*/
private boolean handlePossibleFileOverviewJsDoc(
JsDocInfoParser jsDocParser) {
if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
return true;
}
return false;
}
private void handlePossibleFileOverviewJsDoc(Comment comment) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
comment.setParsed(true);
handlePossibleFileOverviewJsDoc(jsDocParser);
}
private JSDocInfo handleJsDoc(AstNode node) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
comment.setParsed
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>(true);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
return jsDocParser.retrieveAndResetParsedJSDocInfo();
}
}
return null;
}
private Node transform(AstNode node) {
JSDocInfo jsDocInfo = handleJsDoc(node);
Node irNode = justTransform(node);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
// If we have a named function, set the position to that of the name.
if (irNode.getType() == Token.FUNCTION &&
irNode.getFirstChild().getLineno() != -1) {
irNode.setLineno(irNode.getFirstChild().getLineno());
irNode.setCharno(irNode.getFirstChild().getCharno());
} else {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
}
}
return irNode;
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @return A JSDocInfoParser. Will contain either fileoverview jsdoc, or
* normal jsdoc, or no jsdoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
sourceName,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.jscomp.mozilla.rhino.Node n) {
Node node = newNode(transformTokenType(n.getType()));
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>NodeTraversal t,
Map<Var, ReferenceCollection> referenceMap) {
// Check all vars after finishing a scope
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
checkVar(t, v, referenceMap.get(v).references);
}
}
/**
* If the variable is declared more than once in a basic block, generate a
* warning. Also check if a variable is used in a given scope before it is
* declared, which suggest a likely error. Relies on the fact that
* references is in parse-tree order.
*/
private void checkVar(NodeTraversal t, Var v, List<Reference> references) {
blocksWithDeclarations.clear();
boolean isDeclaredInScope = false;
boolean isUnhoistedNamedFunction = false;
Reference hoistedFn = null;
// Look for hoisted functions.
for (Reference reference : references) {
if (reference.isHoistedFunction()) {
blocksWithDeclarations.add(reference.getBasicBlock());
isDeclaredInScope = true;
hoistedFn = reference;
break;
} else if (NodeUtil.isFunctionDeclaration(
reference.getNameNode().getParent())) {
isUnhoistedNamedFunction = true;
}
}
for (Reference reference : references) {
if (reference == hoistedFn) {
continue;
}
BasicBlock basicBlock = reference.getBasicBlock();
boolean isDeclaration = reference.isDeclaration();
if (isDeclaration) {
// Look through all the declarations we've found so far, and
// check if any of them are before this block.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (declaredBlock.provablyExecutesBefore(basicBlock)) {
compiler.report(
JSError.make(reference.getSourceName(),
reference.getNameNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
}
if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) {
// Only allow an unhoisted named function to be used within the
// block it is declared.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (!declaredBlock.provablyExecutesBefore(basicBlock)) {
compiler.report(
JSError.make(reference.getSourceName(),
reference.getNameNode(),
AMBIGUOUS_FUNCTION_DECL, v.name));
break;
}
}
}
if (!isDeclaration && !isDeclaredInScope) {
// Special case to deal with var goog = goog || {}
Node grandparent = reference.getGrandparent();
if (grandparent.getType() == Token.NAME
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
compiler.report(
JSError.make(reference.getSourceName(),
reference.getNameNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>ImplicitPrototype();
if (implicitPrototype != null) {
return implicitPrototype.isPropertyTypeInferred(property);
}
// property does not exist
return false;
}
return p.inferred;
}
@Override
public JSType getPropertyType(String propertyName) {
Property p = properties.get(propertyName);
if (p != null) {
return p.type;
}
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
return implicitPrototype.getPropertyType(propertyName);
}
return getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
@Override
public boolean isPropertyInExterns(String propertyName) {
Property p = properties.get(propertyName);
if (p != null) {
return p.inExterns;
}
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
return implicitPrototype.isPropertyInExterns(propertyName);
}
return false;
}
@Override
boolean defineProperty(String name, JSType type, boolean inferred,
boolean inExterns) {
if (hasOwnDeclaredProperty(name)) {
return false;
}
properties.put(name, new Property(type, inferred, inExterns));
return true;
}
@Override
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
Property p = properties.get(propertyName);
if (p != null) {
return p.docInfo;
}
return null;
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info,
boolean inExterns) {
if (info != null) {
if (!properties.containsKey(propertyName)) {
// If docInfo was attached, but the type of the property
// was not defined anywhere, then we consider this an explicit
// declaration of the property.
defineInferredProperty(propertyName, getPropertyType(propertyName),
inExterns);
}
// The prototype property is not represented as a normal Property.
// We probably don't want to attach any JSDoc to it anyway.
Property property = properties.get(propertyName);
if (property != null) {
property.docInfo = info;
}
}
}
@Override
public boolean matchesNumberContext() {
return isNumberObjectType() || isDateType() || isBooleanObjectType() ||
isStringObjectType() || hasOverridenNativeProperty("valueOf");
}
@Override
public boolean matchesStringContext() {
return isTheObjectType() || isStringObjectType() || isDateType() ||
isRegexpType() || isArrayType() || isNumberObjectType() ||
isBooleanObjectType() || hasOverridenNativeProperty("toString");
}
/**
* Given the name of a native object property, checks whether the property is
* present on the object and different from the native one.
*/
private boolean hasOverridenNativeProperty(String propertyName) {
if (isNative()) {
return false;
}
JSType propertyType = getPropertyType(propertyName);
ObjectType nativeType =
this.isFunctionType() ?
registry.getNativeObjectType(JSTypeNative.FUNCTION_PROTOTYPE) :
registry.getNativeObjectType(JSTypeNative.OBJECT_PROTOTYPE);
JSType nativePropertyType = nativeType.getPropertyType(propertyName);
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> Interfaces
// Find all the interfaces implemented by this class and compare each one
// to the interface instance.
ObjectType thatObj = that.toObjectType();
ObjectType thatCtor = thatObj == null ? null : thatObj.getConstructor();
if (thatCtor != null && thatCtor.isInterface()) {
Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces();
for (ObjectType thisInterface : thisInterfaces) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
}
// other prototype based objects
if (that != null) {
if (isUnknownType() || implicitPrototypeChainIsUnknown()) {
// If unsure, say 'yes', to avoid spurious warnings.
// TODO(user): resolve the prototype chain completely in all cases,
// to avoid guessing.
return true;
}
return this.isImplicitPrototype(thatObj);
}
return false;
}
private boolean implicitPrototypeChainIsUnknown() {
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.isUnknownType()) {
return true;
}
p = p.getImplicitPrototype();
}
return false;
}
private static final class Property implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Property's type.
*/
private JSType type;
/**
* Whether the property's type is inferred.
*/
private final boolean inferred;
/**
* Whether the property is defined in the externs.
*/
private final boolean inExterns;
/** The JSDocInfo for this property. */
private JSDocInfo docInfo = null;
private Property(JSType type, boolean inferred, boolean inExterns) {
this.type = type;
this.inferred = inferred;
this.inExterns = inExterns;
}
}
@Override
public boolean hasCachedValues() {
return super.hasCachedValues();
}
/** Whether this is a built-in object. */
@Override
public boolean isNativeObjectType() {
return nativeType;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
// Don't try to resolve native types, because it's unnecessary and
// there are infinite loops between native types.
if (implicitPrototype != null && !implicitPrototype.isNativeObjectType()) {
implicitPrototype = (ObjectType) implicitPrototype.resolve(t, scope);
}
for (Property prop : properties.values()) {
prop.type = safeResolve(prop.type, t, scope);
}
return this;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> }
// Check if the sources need to be re-ordered.
if (options.manageClosureDependencies) {
for (CompilerInput input : inputs) {
input.setCompiler(this);
// Forward-declare all the provided types, so that they
// are not flagged even if they are dropped from the process.
for (String provide : input.getProvides()) {
getTypeRegistry().forwardDeclareType(provide);
}
}
try {
inputs =
(moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph)
.manageDependencies(
options.manageClosureDependenciesEntryPoints, inputs);
} catch (CircularDependencyException e) {
report(JSError.make(
JSModule.CIRCULAR_DEPENDENCY_ERROR, e.getMessage()));
return null;
} catch (MissingProvideException e) {
report(JSError.make(
MISSING_ENTRY_ERROR, e.getMessage()));
return null;
}
}
// Check if inputs need to be rebuilt from modules.
boolean staleInputs = false;
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
if (hasErrors()) {
return null;
}
// Inputs can have a null AST during initial parse.
if (n == null) {
continue;
}
if (n.getJSDocInfo() != null) {
JSDocInfo info = n.getJSDocInfo();
if (info.isExterns()) {
// If the input file is explicitly marked as an externs file, then
// assume the programmer made a mistake and throw it into
// the externs pile anyways.
externsRoot.addChildToBack(n);
input.setIsExtern(true);
input.getModule().remove(input);
externs.add(input);
staleInputs = true;
} else if (info.isNoCompile()) {
input.getModule().remove(input);
staleInputs = true;
}
}
}
if (staleInputs) {
fillEmptyModules(modules);
rebuildInputsFromModules();
}
// Build the AST.
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
if (n == null) {
continue;
}
if (devMode) {
runSanityCheck();
if (hasErrors()) {
return null;
}
}
if (options.sourceMapOutputPath != null ||
options.nameReferenceReportPath != null) {
// Annotate the nodes in the tree with information from the
// input file. This information is used to construct the SourceMap.
SourceInformationAnnotator sia =
new SourceInformationAnnotator(
input.getName(), options.devMode != DevMode.OFF);
NodeTraversal.traverse(this, n, sia);
}
jsRoot.addChildToBack(n);
}
externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot);
externAndJsRoot.setIsSyntheticBlock(true);
return externAndJsRoot;
} finally {
stopTracer(tracer, "parseInputs");
}
}
public Node parse(JSSourceFile file) {
initCompilerOptionsIfTesting();
addToDebugLog("Parsing: " + file.getName());
return new JsAst
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module: " + module.getName());
}
toSource(cb, i, scriptNode);
}
return cb.toString();
}
});
}
/**
* Converts the parse tree for each input in a module back to js code.
*/
public String[] toSourceArray(final JSModule module) {
return runInCompilerThread(new Callable<String[]>() {
public String[] call() throws Exception {
List<CompilerInput> inputs = module.getInputs();
int numInputs = inputs.size();
if (numInputs == 0) {
return new String[0];
}
String[] sources = new String[numInputs];
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module input: " + inputs.get(i).getName());
}
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
}
});
}
/**
* Writes out js code from a root node. If printing input delimiters, this
* method will attach a comment to the start of the text indicating which
* input the output derived from. If there were any preserve annotations
* within the root's source, they will also be printed in a block comment
* at the beginning of the output.
*/
public void toSource(final CodeBuilder cb,
final int inputSeqNum,
final Node root) {
runInCompilerThread(new Callable<Void>() {
public Void call() throws Exception {
if (options.printInputDelimiter) {
if ((cb.getLength() > 0) && !cb.endsWith("\n")) {
cb.append("\n"); // Make sure that the label starts on a new line
}
Preconditions.checkState(root.getType() == Token.SCRIPT);
String delimiter = options.inputDelimiter;
String sourceName = (String)root.getProp(Node.SOURCENAME_PROP);
Preconditions.checkState(sourceName != null);
Preconditions.checkState(!sourceName.isEmpty());
delimiter = delimiter.replaceAll("%name%", sourceName)
.replaceAll("%num%", String.valueOf(inputSeqNum));
cb.append(delimiter)
.append("\n");
}
if (root.getJSDocInfo() != null &&
root.getJSDocInfo().getLicense() != null) {
cb.append("/*\n")
.append(root.getJSDocInfo().getLicense())
.append("*/\n");
}
// If there is a valid source map, then indicate to it that the current
// root node's mappings are offset by the given string builder buffer.
if (options.sourceMapOutputPath != null) {
sourceMap.setStartingPosition(
cb.getLineIndex(), cb.getColumnIndex());
}
String code = toSource(root, sourceMap);
if (!code.isEmpty()) {
cb.append(code);
if (!code.endsWith(";")) {
cb.append("
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>;");
}
}
return null;
}
});
}
/**
* Generates JavaScript source code for an AST, doesn't generate source
* map info.
*/
@Override
String toSource(Node n) {
initCompilerOptionsIfTesting();
return toSource(n, null);
}
/**
* Generates JavaScript source code for an AST.
*/
private String toSource(Node n, SourceMap sourceMap) {
CodePrinter.Builder builder = new CodePrinter.Builder(n);
builder.setPrettyPrint(options.prettyPrint);
builder.setLineBreak(options.lineBreak);
builder.setSourceMap(sourceMap);
builder.setSourceMapDetailLevel(options.sourceMapDetailLevel);
Charset charset = options.outputCharset != null ?
Charset.forName(options.outputCharset) : null;
builder.setOutputCharset(charset);
return builder.build();
}
/**
* Stores a buffer of text to which more can be appended. This is just like a
* StringBuilder except that we also track the number of lines.
*/
public static class CodeBuilder {
private final StringBuilder sb = new StringBuilder();
private int lineCount = 0;
private int colCount = 0;
/** Removes all text, but leaves the line count unchanged. */
void reset() {
sb.setLength(0);
}
/** Appends the given string to the text buffer. */
CodeBuilder append(String str) {
sb.append(str);
// Adjust the line and column information for the new text.
int index = -1;
int lastIndex = index;
while ((index = str.indexOf('\n', index + 1)) >= 0) {
++lineCount;
lastIndex = index;
}
if (lastIndex == -1) {
// No new lines, append the new characters added.
colCount += str.length();
} else {
colCount = str.length() - (lastIndex + 1);
}
return this;
}
/** Returns all text in the text buffer. */
@Override
public String toString() {
return sb.toString();
}
/** Returns the length of the text buffer. */
public int getLength() {
return sb.length();
}
/** Returns the (zero-based) index of the last line in the text buffer. */
int getLineIndex() {
return lineCount;
}
/** Returns the (zero-based) index of the last column in the text buffer. */
int getColumnIndex() {
return colCount;
}
/** Determines whether the text ends with the given suffix. */
boolean endsWith(String suffix) {
return (sb.length() > suffix.length())
&& suffix.equals(sb.substring(sb.length() - suffix.length()));
}
}
//------------------------------------------------------------------------
// Optimizations
//------------------------------------------------------------------------
public void optimize() {
// Ideally, this pass should be the first pass run, however:
// 1) VariableReferenceCheck reports unexpected warnings if Normalize
// is done first.
// 2) ReplaceMessages, stripCode, and potentially custom passes rely on
// unmodified local names.
normalize();
PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker);
if (options.devMode == DevMode.EVERY_PASS) {
phase
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
* @since 1.6R6
*/
public String getScriptStackTrace()
{
return getScriptStackTrace(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".js");
}
});
}
/**
* Get a string representing the script stack of this exception.
* If optimization is enabled, this corresponds to all java stack elements
* with a source name matching the <code>filter</code>.
* @param filter the file name filter to determine whether a file is a
* script file
* @return a script stack dump
* @since 1.6R6
*/
public String getScriptStackTrace(FilenameFilter filter)
{
// The real Rhino code here has been removed.
return "<No stack trace available>";
}
@Override public void printStackTrace(PrintWriter s)
{
if (interpreterStackInfo == null) {
super.printStackTrace(s);
} else {
s.print(generateStackTrace());
}
}
@Override public void printStackTrace(PrintStream s)
{
if (interpreterStackInfo == null) {
super.printStackTrace(s);
} else {
s.print(generateStackTrace());
}
}
private String sourceName;
private int lineNumber;
private String lineSource;
private int columnNumber;
Object interpreterStackInfo;
int[] interpreterLineData;
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> are
* declared or inferred.
*
*/
public abstract class ObjectType extends JSType {
private boolean visited;
private JSDocInfo docInfo = null;
private boolean unknown = true;
ObjectType(JSTypeRegistry registry) {
super(registry);
}
/**
* Gets the declared default element type.
* @see ParameterizedType
*/
public JSType getParameterType() {
return null;
}
/**
* Gets the declared default index type.
* @see IndexedType
*/
public JSType getIndexType() {
return null;
}
/**
* Gets the docInfo for this type.
*/
@Override public JSDocInfo getJSDocInfo() {
if (docInfo != null) {
return docInfo;
} else if (getImplicitPrototype() != null) {
return getImplicitPrototype().getJSDocInfo();
} else {
return super.getJSDocInfo();
}
}
/**
* Sets the docInfo for this type from the given
* {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}.
*/
public void setJSDocInfo(JSDocInfo info) {
docInfo = info;
}
/**
* Detects a cycle in the implicit prototype chain. This method accesses
* the {@link #getImplicitPrototype()} method and must therefore be
* invoked only after the object is sufficiently initialized to respond to
* calls to this method.<p>
*
* The method is not thread safe.<p>
*
* @return True iff an implicit prototype cycle was detected.
*/
final boolean detectImplicitPrototypeCycle() {
// detecting cycle
this.visited = true;
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.visited) {
return true;
} else {
p.visited = true;
}
p = p.getImplicitPrototype();
}
// clean up
p = this;
do {
p.visited = false;
p = p.getImplicitPrototype();
} while (p != null);
return false;
}
/**
* Gets the reference name for this object. This includes named types
* like constructors, prototypes, and enums. It notably does not include
* literal types like strings and booleans and structural types.
* @return the object's name or {@code null} if this is an anonymous
* object
*/
public abstract String getReferenceName();
/**
* Due to the complexity of some of our internal type systems, sometimes
* we have different types constructed by the same constructor.
* In other parts of the type system, these are called delegates.
* We construct these types by appending suffixes to the constructor name.
*
* The normalized reference name does not have these suffixes, and as such,
* recollapses these implicit types back to their real type.
*/
public String getNormalizedReferenceName() {
String name = getReferenceName();
if (name != null) {
int pos = name.indexOf("(");
if (pos != -1) {
return name.substring(0, pos);
}
}
return name;
}
/**
* Creates a suffix for a proxy delegate.
* @see #getNormalizedReferenceName
*/
public static String
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> * property could be called, so setting this incorrectly could result
* in live code being removed.
* @return True if the property was registered successfully, false if this
* conflicts with a previous property type declaration.
*/
abstract boolean defineProperty(String propertyName, JSType type,
boolean inferred, boolean inExterns);
/**
* Gets the docInfo on the specified property on this type. This should not
* be done implemented recursively, as you generally need to know exactly on
* which type in the prototype chain the JSDocInfo exists.
*/
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
return null;
}
/**
* Sets the docInfo for the specified property from the
* {@link JSDocInfo} on its definition.
* @param info {@code JSDocInfo} for the property definition. May be
* {@code null}.
* @param inExterns {@code true} if this property was defined in an externs
* file. TightenTypes assumes that any function passed to an externs
* property could be called, so setting this incorrectly could result
* in live code being removed.
*/
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info,
boolean inExterns) {
// by default, do nothing
}
@Override
public JSType findPropertyType(String propertyName) {
return hasProperty(propertyName) ?
getPropertyType(propertyName) : null;
}
/**
* Gets the property type of the property whose name is given. If the
* underlying object does not have this property, the Unknown type is
* returned to indicate that no information is available on this property.
*
* @return the property's type or {@link UnknownType}. This method never
* returns {@code null}.
*/
public abstract JSType getPropertyType(String propertyName);
/**
* Checks whether the property whose name is given is present on the
* object.
*/
public abstract boolean hasProperty(String propertyName);
/**
* Checks whether the property whose name is given is present directly on
* the object. Returns false even if it is declared on a supertype.
*/
public boolean hasOwnProperty(String propertyName) {
return hasProperty(propertyName);
}
/** Returns the names of all the properties directly on this type. */
public Set<String> getOwnPropertyNames() {
return ImmutableSet.of();
}
/**
* Checks whether the property's type is inferred.
*/
public abstract boolean isPropertyTypeInferred(String propertyName);
/**
* Checks whether the property's type is declared.
*/
public abstract boolean isPropertyTypeDeclared(String propertyName);
/**
* Whether the given property is declared on this object.
*/
boolean hasOwnDeclaredProperty(String name) {
return hasOwnProperty(name) && isPropertyTypeDeclared(name);
}
/** Checks whether the property was defined in the externs. */
public boolean isPropertyInExterns(String propertyName) {
return false;
}
/**
* Gets the number of properties of this object.
*/
public abstract int getPropertiesCount();
/**
* Returns a list of properties defined or inferred on this type and any of
* its supertypes.
*/
public Set<String> getPropertyNames() {
Set<String> props = Sets.new
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnName, fnNameNode, n, null, null, n);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a.getString(), a, args, n, null, n);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
Preconditions.checkState(child.getType() == Token.NAME);
String name = child.getString();
declareVar(name, child, n, parent, null, n);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(fnName, n.getFirstChild(), n, parent, null, n);
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var.getString(), var, n, parent, null, n);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>) {
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.getType() == Token.CATCH &&
parent.getType() == Token.CATCH) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = false;
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
if (!allowDupe) {
compiler.report(
JSError.make(sourceName, nodeWithLineNumber,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(sourceName, nodeWithLineNumber,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param name The variable name
* @param n The node corresponding to the variable name (usually a NAME node)
* @param parent The parent node of {@code n}
* @param gramps The parent node of {@code parent}
* @param declaredType The variable's type, according to JSDoc
* @param nodeWithLineNumber The node to use to access the line number of
* the variable declaration, if needed
*/
private void declareVar(String name, Node n, Node parent,
Node gramps, JSType declaredType,
Node nodeWithLineNumber) {
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, parent, gramps, nodeWithLineNumber);
} else {
scope.declare(name, n, declaredType, compiler.getInput(sourceName));
}
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.nio.charset.Charset;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) {
super(consumer, outputCharset);
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.getType() == Token.BLOCK
|| parent.getType() == Token.SCRIPT)) {
if (n.getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n));
} else if (n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.getType() == Token.VAR
&& n.getFirstChild().getFirstChild() != null
&& n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n.getFirstChild().getFirstChild()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
JSType type = node.getJSType();
if (type instanceof FunctionType) {
return getFunctionAnnotation(node);
} else if (type != null && !type.isUnknownType()
&& !type.isEmptyType() && !type.isVoidType() &&
!type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType() + "} */\n";
} else {
return "";
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
*/
private String getFunctionAnnotation(Node fnNode) {
Preconditions.checkState(fnNode.getType() == Token.FUNCTION);
StringBuilder sb = new StringBuilder("/**\n");
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = (FunctionType) fnNode.getJSType();
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// LP
// NAME param1
// NAME param2
if (fnNode != null) {
Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild();
// Param types
for (Node n : funType.getParameters()) {
// Bail out if the paramNode is not there.
if (paramNode == null) {
break;
}
sb.append(" * @param {" + getParameterNodeJSDocType(n) + "} ");
sb.append(paramNode.getString());
sb.append("\n");
paramNode = paramNode.getNext();
}
}
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) {
sb.append(" * @return {" + retType + "}\n");
}
// Constructor/interface
if (funType.isConstructor() || funType.isInterface()) {
FunctionType superConstructor = funType.getSuperClassConstructor();
if (superConstructor != null) {
ObjectType superInstance =
funType.getSuperClassConstructor().getInstanceType();
if (!superInstance.toString().equals("Object")) {
sb.append(" * @extends {" + superInstance + "}\n");
}
}
// Avoid duplicates, add implemented type to a set first
Set<String> interfaces = Sets.newTreeSet();
for (ObjectType interfaze : funType.getImplementedInterfaces()) {
interfaces.add(interfaze.toString());
}
for (String interfaze : interfaces) {
sb.append(" * @implements {" + interfaze + "}\n");
}
if (funType.isConstructor()) {
sb.append(" * @constructor\n");
} else if (funType.isInterface()) {
sb.append(" * @interface\n");
}
}
if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) {
sb.append(" * @javadispatch\n");
}
sb.append(" */\n");
return sb.toString();
}
/**
* Creates a JSDoc-suitable String representation the type of a parameter.
*
* @param parameterNode The parameter node.
*/
private String getParameterNodeJSDocType(Node parameterNode) {
JSType parameterType = parameterNode.getJSType();
String typeString;
// Emit unknown types as '*' (AllType) since '?' (UnknownType) is not
// a valid JSDoc type.
if (parameterType.isUnknownType()) {
typeString = "*";
} else {
// Fix-up optional and vararg parameters to match JSDoc type language
if (parameterNode.isOptionalArg()) {
typeString = parameterType.restrictByNotNullOrUndefined() + "=";
} else if (parameterNode.isVarArgs()) {
typeString = "..." + parameterType.restrictByNotNullOrUndefined();
} else {
typeString = parameterType.toString();
}
}
return typeString;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> null) {
referencedObjType.collectPropertyNames(props);
}
}
@Override
public JSType findPropertyType(String propertyName) {
return referencedType.findPropertyType(propertyName);
}
@Override
public JSType getPropertyType(String propertyName) {
return referencedObjType == null ?
getNativeType(JSTypeNative.UNKNOWN_TYPE) :
referencedObjType.getPropertyType(propertyName);
}
@Override
public JSDocInfo getJSDocInfo() {
return referencedType.getJSDocInfo();
}
@Override
public void setJSDocInfo(JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setJSDocInfo(info);
}
}
@Override
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
return referencedObjType == null ? null :
referencedObjType.getOwnPropertyJSDocInfo(propertyName);
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info,
boolean inExterns) {
if (referencedObjType != null) {
referencedObjType.setPropertyJSDocInfo(propertyName, info, inExterns);
}
}
@Override
public boolean hasProperty(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.hasProperty(propertyName);
}
@Override
public boolean hasOwnProperty(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.hasOwnProperty(propertyName);
}
@Override
public Set<String> getOwnPropertyNames() {
return referencedObjType == null ? ImmutableSet.<String>of() :
referencedObjType.getOwnPropertyNames();
}
@Override
public FunctionType getConstructor() {
return referencedObjType == null ? null :
referencedObjType.getConstructor();
}
@Override
public JSType getParameterType() {
return referencedObjType == null ? null :
referencedObjType.getParameterType();
}
@Override
public JSType getIndexType() {
return referencedObjType == null ? null :
referencedObjType.getIndexType();
}
@Override
public <T> T visit(Visitor<T> visitor) {
return referencedType.visit(visitor);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setReferencedType(referencedType.resolve(t, scope));
return this;
}
@Override
public String toDebugHashCodeString() {
return "{proxy:" + referencedType.toDebugHashCodeString() + "}";
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> been deprecated: {1}");
static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_GLOBAL_ACCESS",
"Access to private variable {0} not allowed outside file {1}.");
static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_PROPERTY_ACCESS",
"Access to private property {0} of {1} not allowed here.");
static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PROTECTED_PROPERTY_ACCESS",
"Access to protected property {0} of {1} not allowed here.");
static final DiagnosticType PRIVATE_OVERRIDE =
DiagnosticType.disabled(
"JSC_PRIVATE_OVERRIDE",
"Overriding private property of {0}.");
static final DiagnosticType VISIBILITY_MISMATCH =
DiagnosticType.disabled(
"JSC_VISIBILITY_MISMATCH",
"Overriding {0} property of {1} with {2} property.");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
}
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth++;
}
if (methodDepth == 0) {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.getType() == Token.ASSIGN) {
Node lValue = parent.getFirstChild();
if (lValue.isQualifiedName()) {
if (lValue.getType() == Token.GETPROP) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return ((FunctionType) lValueType).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.getType() == Token.NAME) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isConstructor()) {
return ((FunctionType) type).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR ||
parent.getType() == Token.NEW) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.getType() == Token.NEW) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE &&
!t.getInput().getName().equals(docInfo.getSourceName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), docInfo.getSourceName()));
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = t.inGlobalScope() &&
parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
boolean sameInput =
t.getInput().getName().equals(docInfo.getSourceName());
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || ownerType.differsFrom(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
}
}
}
}
}
/**
* Whether the given access of a private constructor is legal.
*
* For example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return parent.getType() != Token.NEW;
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t The current traversal.
* @param n The node which
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) ||
n.getType() == Token.NEW)) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.getType() == Token.GETPROP && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently ok to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns the deprecation reason for the property if it is marked
* as being deprecated. Returns empty string if the property is deprecated
*
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> but no reason was given. Returns null if the property is not deprecated.
*/
private static String getPropertyDeprecationInfo(ObjectType type,
String prop) {
JSDocInfo info = type.getOwnPropertyJSDocInfo(prop);
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType implicitProto = type.getImplicitPrototype();
if (implicitProto != null) {
return getPropertyDeprecationInfo(implicitProto, prop);
}
return null;
}
/**
* Dereference a type, autoboxing it and filtering out null.
*/
private static JSType dereference(JSType type) {
return type == null ? null : type.dereference();
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>externs, root);
}
};
}
};
/** Checks type usage */
private final PassFactory checkTypes =
new PassFactory("checkTypes", false) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(typedScopeCreator);
TypeCheck check = makeTypeCheck(compiler);
check.process(externs, root);
compiler.getErrorManager().setTypedPercent(check.getTypedPercent());
}
};
}
};
/**
* Checks possible execution paths of the program for problems: missing return
* statements and dead code.
*/
private final PassFactory checkControlFlow =
new PassFactory("checkControlFlow", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
List<Callback> callbacks = Lists.newArrayList();
if (options.checkUnreachableCode.isOn()) {
callbacks.add(
new CheckUnreachableCode(compiler, options.checkUnreachableCode));
}
if (options.checkMissingReturn.isOn() && options.checkTypes) {
callbacks.add(
new CheckMissingReturn(compiler, options.checkMissingReturn));
}
return combineChecks(compiler, callbacks);
}
};
/** Checks access controls. Depends on type-inference. */
private final PassFactory checkAccessControls =
new PassFactory("checkAccessControls", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckAccessControls(compiler);
}
};
/** Executes the given callbacks with a {@link CombinedCompilerPass}. */
private static CompilerPass combineChecks(AbstractCompiler compiler,
List<Callback> callbacks) {
Preconditions.checkArgument(callbacks.size() > 0);
Callback[] array = callbacks.toArray(new Callback[callbacks.size()]);
return new CombinedCompilerPass(compiler, array);
}
/** A compiler pass that resolves types in the global scope. */
private class GlobalTypeResolver implements CompilerPass {
private final AbstractCompiler compiler;
GlobalTypeResolver(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (topScope == null) {
typedScopeCreator =
new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = typedScopeCreator.createScope(root.getParent(), null);
} else {
compiler.getTypeRegistry().resolveTypesInScope(topScope);
}
}
}
/** Checks global name usage. */
private final PassFactory checkGlobalNames =
new PassFactory("Check names", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
// Create a global namespace for analysis by check passes.
// Note that this class does all heavy computation lazily,
// so it's OK to create it here.
namespaceForChecks = new GlobalNamespace(compiler, jsRoot);
new CheckGlobalNames(compiler, options.checkGlobalNamesLevel)
.injectNamespace(namespaceForChecks).process(externs, jsRoot);
}
};
}
};
/** Checks
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>_TYPE);
if (anyObjectType.isSubtype(that)) {
return anyObjectType;
} else if (that.isSubtype(anyObjectType)) {
return that;
} else {
return getNativeType(JSTypeNative.NO_TYPE);
}
}
public JSType caseAllType() {
return getNativeType(JSTypeNative.NO_OBJECT_TYPE);
}
public JSType caseVoidType() {
return getNativeType(JSTypeNative.NO_OBJECT_TYPE);
}
public JSType caseEnumElementType(EnumElementType type) {
return type.getPrimitiveType().visit(this);
}
}
NoObjectType(JSTypeRegistry registry) {
super(registry, null, null,
registry.createArrowType(null, null),
null, null, true, true);
getInternalArrowType().returnType = this;
this.setInstanceType(this);
}
@Override
public TernaryValue testForEquality(JSType that) {
return that.isEmptyType() ? TernaryValue.TRUE : TernaryValue.UNKNOWN;
}
@Override
public boolean isSubtype(JSType that) {
if (JSType.isSubtype(this, that)) {
return true;
} else {
return that.isObject() && !that.isNoType();
}
}
@Override
public boolean isFunctionType() {
return false;
}
@Override
public boolean isNoObjectType() {
return true;
}
@Override
public JSType getLeastSupertype(JSType that) {
return that.visit(leastSupertypeVisitor);
}
@Override
public JSType getGreatestSubtype(JSType that) {
return that.visit(greatestSubtypeVisitor);
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public String getReferenceName() {
return null;
}
@Override
public boolean matchesNumberContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public boolean isEquivalentTo(JSType that) {
return this == that;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public int getPropertiesCount() {
// Should never be called, returning the biggest number to highlight the
// 'unifying' role of this type.
return Integer.MAX_VALUE;
}
@Override
public JSType getPropertyType(String propertyName) {
// Return the least type to be a proper subtype of all other objects.
return getNativeType(JSTypeNative.NO_TYPE);
}
@Override
public boolean hasProperty(String propertyName) {
// has all properties, since it is any object
return true;
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, boolean inExterns) {
// nothing, all properties are defined
return true;
}
@Override
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
return null;
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info,
boolean inExterns) {
// Do nothing, specific properties
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.parsing;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.mozilla.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.ScriptRuntime;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A parser for JSDoc comments.
*
*/
// TODO(nicksantos): Unify all the JSDocInfo stuff into one package, instead of
// spreading it across mutliple packages.
public final class JsDocInfoParser {
private final JsDocTokenStream stream;
private final JSDocInfoBuilder jsdocBuilder;
private final String sourceName;
private final ErrorReporter errorReporter;
private final ErrorReporterParser parser = new ErrorReporterParser();
private class ErrorReporterParser {
void addWarning(String messageId, String messageArg, int lineno,
int charno) {
errorReporter.warning(ScriptRuntime.getMessage1(messageId, messageArg),
sourceName, lineno, null, charno);
}
void addWarning(String messageId, int lineno, int charno) {
errorReporter.warning(ScriptRuntime.getMessage0(messageId),
sourceName, lineno, null, charno);
}
}
// The DocInfo with the fileoverview tag for the whole file.
private JSDocInfo fileOverviewJSDocInfo = null;
private State state;
private final Map<String, Annotation> annotationNames;
private final Set<String> suppressionNames;
static private final Set<String> modifiesAnnotationKeywords =
ImmutableSet.<String>of("this", "arguments");
private Node.FileLevelJsDocBuilder fileLevelJsDocBuilder;
/**
* Sets the JsDocBuilder for the file-level (root) node of this parse. The
* parser uses the builder to append any preserve annotations it encounters
* in jsdoc comments.
*
* @param fileLevelJsDocBuilder
*/
void setFileLevelJsDocBuilder(
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder) {
this.
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>fileLevelJsDocBuilder = fileLevelJsDocBuilder;
}
/**
* Sets the file overview JSDocInfo, in order to warn about multiple uses of
* the @fileoverview tag in a file.
*/
void setFileOverviewJSDocInfo(JSDocInfo fileOverviewJSDocInfo) {
this.fileOverviewJSDocInfo = fileOverviewJSDocInfo;
}
private enum State {
SEARCHING_ANNOTATION,
SEARCHING_NEWLINE,
NEXT_IS_ANNOTATION
}
JsDocInfoParser(JsDocTokenStream stream,
String sourceName,
Config config,
ErrorReporter errorReporter) {
this.stream = stream;
this.sourceName = sourceName;
this.jsdocBuilder = new JSDocInfoBuilder(config.parseJsDocDocumentation);
this.annotationNames = config.annotationNames;
this.suppressionNames = config.suppressionNames;
this.errorReporter = errorReporter;
}
/**
* Parses a string containing a JsDoc type declaration, returning the
* type if the parsing succeeded or {@code null} if it failed.
*/
public static Node parseTypeString(String typeString) {
Config config = new Config(
Sets.<String>newHashSet(),
Sets.<String>newHashSet(),
false);
JsDocInfoParser parser = new JsDocInfoParser(
new JsDocTokenStream(typeString),
"typeparsing",
config,
NullErrorReporter.forNewRhino());
return parser.parseTopLevelTypeExpression(parser.next());
}
/**
* Parses a {@link JSDocInfo} object. This parsing method reads all tokens
* returned by the {@link JsDocTokenStream#getJsDocToken()} method until the
* {@link JsDocToken#EOC} is returned.
*
* @return {@code true} if JSDoc information was correctly parsed,
* {@code false} otherwise
*/
boolean parse() {
int lineno;
int charno;
// JSTypes are represented as Rhino AST nodes, and then resolved later.
JSTypeExpression type;
state = State.SEARCHING_ANNOTATION;
skipEOLs();
JsDocToken token = next();
// Always record that we have a comment.
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo blockInfo = extractBlockComment(token);
token = blockInfo.token;
if (!blockInfo.string.isEmpty()) {
jsdocBuilder.recordBlockDescription(blockInfo.string);
}
} else {
if (token != JsDocToken.ANNOTATION &&
token != JsDocToken.EOC) {
// Mark that there was a description, but don't bother marking
// what it was.
jsdocBuilder.recordBlockDescription("");
}
}
// Parse the actual JsDoc.
retry: for (;;) {
switch (token) {
case ANNOTATION:
if (state == State.SEARCHING_ANNOTATION) {
state = State.SEARCHING_NEWLINE;
lineno = stream.getLineno();
charno = stream.getCharno();
String annotationName = stream.getString();
Annotation annotation = annotationNames.get(annotationName);
if (annotation == null) {
parser.addWarning("msg.bad.jsdoc.tag", annotation
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>ips all EOLs and all empty lines in the JSDoc. Call this method if you
* want the JSDoc entry to span multiple lines.
*/
private void skipEOLs() {
while (match(JsDocToken.EOL)) {
next();
if (match(JsDocToken.STAR)) {
next();
}
}
}
/**
* Determines whether the parser has been populated with docinfo with a
* fileoverview tag.
*/
private boolean hasParsedFileOverviewDocInfo() {
return jsdocBuilder.isPopulatedWithFileOverview();
}
boolean hasParsedJSDocInfo() {
return jsdocBuilder.isPopulated();
}
JSDocInfo retrieveAndResetParsedJSDocInfo() {
return jsdocBuilder.build(sourceName);
}
/**
* Gets the fileoverview JSDocInfo, if any.
*/
JSDocInfo getFileOverviewJSDocInfo() {
return fileOverviewJSDocInfo;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* CodingConvention defines a set of hooks to customize the behavior of the
* Compiler for a specific team/company.
*
*/
public class DefaultCodingConvention implements CodingConvention {
private static final long serialVersionUID = 1L;
@Override
public boolean isConstant(String variableName) {
return false;
}
@Override
public boolean isConstantKey(String variableName) {
return false;
}
@Override
public boolean isValidEnumKey(String key) {
return key != null && key.length() > 0;
}
@Override
public boolean isOptionalParameter(Node parameter) {
// be as lax as possible, but this must be mutually exclusive from
// var_args parameters.
return !isVarArgsParameter(parameter);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
// be as lax as possible
return parameter.getParent().getLastChild() == parameter;
}
@Override
public boolean isExported(String name, boolean local) {
return local && name.startsWith("$super");
}
@Override
public boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return false;
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String getExportPropertyFunction() {
return null;
}
@Override
public String getExportSymbolFunction() {
return null;
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return null;
}
@Override
public String identifyTypeDefAssign(Node n) {
return null;
}
@Override
public
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
/** Warnings guards for each suppressable warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressable groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressableGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressableGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
// We only care about function annotations at the FUNCTION and SCRIPT
// level. Otherwise, the @suppress annotation has an implicit
// dependency on the exact structure of our AST, and that seems like
// a bad idea.
if (type == Token.FUNCTION) {
info = NodeUtil.getFunctionInfo(current);
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
// Some @suppress tags are for other tools, and
// may not have a warnings guard.
if (guard != null) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
return newLevel;
}
}
}
}
}
}
return null;
}
@Override
public int getPriority() {
// Happens after path-based filtering, but before other times
// of filtering.
return WarningsGuard.Priority.SUPPRESS_DOC.value;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2004 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains utilities that get properties from the Node object.
*
*/
public final class NodeUtil {
// TODO(user): Eliminate this class and make all of the static methods
// instance methods of com.google.javascript.rhino.Node.
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getExpressionBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getExpressionBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getExpressionBooleanValue(n.getLastChild());
return value.not();
case Token.AND: {
TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild());
TernaryValue rhs = getExpressionBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild());
TernaryValue rhs = getExpressionBooleanValue(n.getLastChild
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Node parent = n.getParent();
String name = n.getFirstChild().getString();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getString();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
return name != null && name.length() != 0 ? name : null;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getNearestFunctionName(Node n) {
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.STRING:
// Return the name of the literal's key.
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> not read
* any captured vairables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Uniary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (block.getType() != Token.BLOCK) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (n.getType() != Token.EMPTY) {
return false;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>.GETPROP) {
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Functions in the "Math" namespace have no side effects.
if (nameNode.getFirstChild().getType() == Token.NAME) {
String namespaceName = nameNode.getFirstChild().getString();
if (namespaceName.equals("Math")) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().getType() == Token.REGEXP
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().getType() == Token.STRING
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.getType() == Token.STRING
|| param.getType() == Token.REGEXP))
return false;
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> referencesThis(Node n) {
return containsType(n, Token.THIS, new MatchNotFunction());
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.getType() == Token.GETPROP
|| n.getType() == Token.GETELEM;
}
/**
* Is this a GETPROP node?
*/
static boolean isGetProp(Node n) {
return n.getType() == Token.GETPROP;
}
/**
* Is this a NAME node?
*/
static boolean isName(Node n) {
return n.getType() == Token.NAME;
}
/**
* Is this a NEW node?
*/
static boolean isNew(Node n) {
return n.getType() == Token.NEW;
}
/**
* Is this a VAR node?
*/
static boolean isVar(Node n) {
return n.getType() == Token.VAR;
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR;
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(isName(n));
Node parent = n.getParent();
if (isVar(parent)) {
return n.getFirstChild();
} else if (isAssign(parent) && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this a STRING node?
*/
static boolean isString(Node n) {
return n.getType() == Token.STRING;
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN;
}
/**
* Is this an ASSIGN node?
*/
static boolean isAssign(Node n) {
return n.getType() == Token.ASSIGN;
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.CALL;
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.getType() == Token.FOR
&& n.getChildCount() == 3;
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (NodeUtil.isFunction(parent)) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK;
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
Node parent = n.getParent();
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.getType() == Token.CASE || n.getType() == Token.DEFAULT;
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return isName(n) && !n.getString().isEmpty();
}
/** @return Whether the node is a label name. */
static boolean isLabelName(Node n) {
return (n != null && n.getType() == Token.LABEL_NAME);
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.getType() == Token.TRY && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
// Node parent = node.getParent();
if (isStatementBlock(parent)
|| isSwitchCase(node)
|| isTryFinallyNode(parent, node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.getType() == Token.VAR) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (node.getType() == Token.BLOCK) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (parent.getType() == Token.LABEL
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>.getParent(), parent);
} else if (parent.getType() == Token.FOR
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, new Node(Token.EMPTY));
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of "+ parent.toString());
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.getType() == Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;)
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>, Node parent) {
return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) ||
parent.getType() == Token.VAR;
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
// TODO(nicksantos): What about NUMBER?
return node.getType() == Token.STRING &&
parent.getType() == Token.OBJECTLIT;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return "||";
case Token.BITXOR: return "^";
case Token.AND: return "&&";
case Token.BITAND: return "&";
case Token.SHEQ: return "===";
case Token.EQ: return "==";
case Token.NOT: return "!";
case Token.NE: return "!=";
case Token.SHNE: return "!==";
case Token.LSH: return "<<";
case Token.IN: return "in";
case Token.LE: return "<=";
case Token.LT: return "<";
case Token.URSH: return ">>>";
case Token.RSH: return ">>";
case Token.GE: return ">=";
case Token.GT: return ">";
case Token.MUL: return "*";
case Token.DIV: return "/";
case Token.MOD: return "%";
case Token.BITNOT: return "~";
case Token.ADD: return "+";
case Token.SUB: return "-";
case Token.POS: return "+";
case Token.NEG: return "-";
case Token.ASSIGN: return "=";
case Token.ASSIGN_BITOR: return "|=";
case Token.ASSIGN_BITXOR: return "^=";
case Token.ASSIGN_BITAND: return "&=";
case Token.ASSIGN_LSH: return "<<=";
case Token.ASSIGN_RSH: return ">>=";
case Token.ASSIGN_URSH: return ">>>=";
case Token.ASSIGN_ADD: return "+=";
case Token.ASSIGN_SUB: return "-=";
case Token.ASSIGN_MUL: return "*=";
case Token.ASSIGN_DIV: return "/=";
case Token.ASSIGN_MOD: return "%=";
case Token.VOID: return "void";
case Token.TYPEOF: return "typeof";
case Token.INSTANCEOF: return "instanceof";
default: return null;
}
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation or fails.
*
* @param operator the operator's token value to convert
* @return the string representation
* @throws Error if the token value is not an operator
*/
static String opToStrNoFail(
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>int operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = new Node(
Token.VAR,
Node.newString(Token.NAME, nameNode.getString())
.copyInformationFrom(nameNode))
.copyInformationFrom(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.getType() == Token.BLOCK ||
addingRoot.getType() == Token.SCRIPT);
Preconditions.checkState(addingRoot.getFirstChild() == null ||
addingRoot.getFirstChild().getType() != Token.SCRIPT);
return addingRoot;
}
/** Creates function name(params_0, ..., params_n) { body }. */
public static Node newFunctionNode(String name, List<Node> params,
Node body, int lineno, int charno) {
Node parameterParen = new Node(Token.LP, lineno, charno);
for (Node param : params) {
parameterParen.addChildToBack(param);
}
Node function = new Node(Token.FUNCTION, lineno, charno);
function.addChildrenToBack(
Node.newString(Token.NAME,
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
*
* @return The node created.
*/
static Node newName(String name, Node basisNode) {
Node nameNode = Node.newString(Token.NAME, name);
nameNode.copyInformationFrom(basisNode);
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return The node created.
*/
static Node newName(String name, Node basisNode, String originalName) {
Node nameNode = newName(name, basisNode);
nameNode.putProp(Node.ORIGINALNAME_PROP, originalName);
return nameNode;
}
/** Test if all characters in the string are in the Basic Latin (aka ASCII)
* character set - that they have UTF-16 values equal to or below 0x7f.
* This check can find which identifiers with Unicode characters need to be
* escaped in order to allow resulting files to be processed by non-Unicode
* aware UNIX tools and editors.
* *
* See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
* for more on Basic Latin.
*
* @param s The string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
char LARGEST_BASIC_LATIN = 0x7f;
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
public void visit(Node n) {
if (n.getType() == Token.NAME) {
Node parent = n.getParent();
if (parent != null && parent.getType() == Token.VAR) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> = type;
}
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.getType() == Token.VAR;
}
}
/**
* A predicate for matching anything except function nodes.
*/
static class MatchNotFunction implements Predicate<Node>{
public boolean apply(Node n) {
return !isFunction(n);
}
}
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
public boolean apply(Node n) {
Node parent = n.getParent();
return n.getType() == Token.BLOCK
|| (!isFunction(n) && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (has(c, pred, traverseChildrenPred)) {
return true;
}
}
return false;
}
/**
* @return The number of times the the predicate is true for the node
* or any of its children.
*/
static int getCount(
Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) {
int total = 0;
if (pred.apply(n)) {
total++;
}
if (traverseChildrenPred.apply(n)) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
total += getCount(c, pred, traverseChildrenPred);
}
}
return total;
}
/**
* Interface for use with the visit method.
* @see #visit
*/
static interface Visitor {
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> Node node, Node parent) {
String name = node.getString();
if (parent.getType() == Token.GETPROP &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* @param nameNode A name node
* @return The JSDocInfo for the name node
*/
static JSDocInfo getInfoForNameNode(Node nameNode) {
JSDocInfo info = null;
Node parent = null;
if (nameNode != null) {
info = nameNode.getJSDocInfo();
parent = nameNode.getParent();
}
if (info == null && parent != null &&
((parent.getType() == Token.VAR && parent.hasOneChild()) ||
parent.getType() == Token.FUNCTION)) {
info = parent.getJSDocInfo();
}
return info;
}
/**
* Get the JSDocInfo for a function.
*/
static JSDocInfo getFunctionInfo(Node n) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.getType() == Token.ASSIGN) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
n = n.getParent();
}
return sourceName;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = isName(callTarget);
Node call = new Node(Token.CALL, callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value) || locals.apply(value);
case Token.NEW:
return true;
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
* Copyright 2004 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeUtil.MatchNotFunction;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
/**
* CodeGenerator generates codes from a parse tree, sending it to the specified
* CodeConsumer.
*
*/
class CodeGenerator {
private static final char[] HEX_CHARS
= { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private final CodeConsumer cc;
private final CharsetEncoder outputCharsetEncoder;
CodeGenerator(
CodeConsumer consumer, Charset outputCharset) {
cc = consumer;
if (outputCharset == null || outputCharset == Charsets.US_ASCII) {
// If we want our default (pretending to be UTF-8, but escaping anything
// outside of straight ASCII), then don't use the encoder, but
// just special-case the code. This keeps the normal path through
// the code identical to how it's been for years.
this.outputCharsetEncoder = null;
} else {
this.outputCharsetEncoder = outputCharset.newEncoder();
}
}
CodeGenerator(CodeConsumer consumer) {
this(consumer, null);
}
void add(String str) {
cc.add(str);
}
private void addIdentifier(String identifier) {
cc.addIdentifier(identifierEscape(identifier));
}
void add(Node n) {
add(n, Context.OTHER);
}
void add(Node n, Context context) {
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p, context);
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> cc.addOp(opstr, true);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
// Handle associativity.
// e.g. if the parse tree is a * (b * c),
// we can simply generate a * b * c.
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(last, p, rhsContext);
} else {
addExpr(last, p + 1, rhsContext);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
Preconditions.checkState(first.getNext().getType() == Token.BLOCK &&
!first.getNext().hasMoreThanOneChild());
Preconditions.checkState(childCount >= 2 && childCount <= 3);
add("try");
add(first, Context.PRESERVE_BLOCK);
// second child contains the catch block, or nothing if there
// isn't a catch block
Node catchblock = first.getNext().getFirstChild();
if (catchblock != null) {
add(catchblock);
}
if (childCount == 3) {
add("finally");
add(last, Context.PRESERVE_BLOCK);
}
break;
}
case Token.CATCH:
Preconditions.checkState(childCount == 2);
add("catch(");
add(first);
add(")");
add(last, Context.PRESERVE_BLOCK);
break;
case Token.THROW:
Preconditions.checkState(childCount == 1);
add("throw");
add(first);
// Must have a ';' after a throw statement, otherwise safari can't
// parse this.
cc.endStatement(true);
break;
case Token.RETURN:
add("return");
if (childCount == 1) {
add(first);
} else {
Preconditions.checkState(childCount == 0);
}
cc.endStatement();
break;
case Token.VAR:
if (first != null) {
add("var ");
addList(first, false, getContextForNoInOperator(context));
}
break;
case Token.LABEL_NAME:
Preconditions.checkState(!n.getString().isEmpty());
addIdentifier(n.getString());
break;
case Token.NAME:
if (first == null || first.getType() == Token.EMPTY) {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.getType() == Token.COMMA) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN));
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator(context));
}
}
break;
case Token.ARRAYLIT:
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
add("[");
addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP));
add("]");
break;
case Token.LP:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
Preconditions.checkState(childCount == 2);
addList(first, false, context);
break;
case Token.NUMBER:
Preconditions.checkState(
childCount ==
((n.getParent() != null &&
n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0));
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type));
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), 1);
cc.addOp(":", true);
addExpr(last, 1);
break;
}
case Token.REGEXP:
if (first.getType() != Token.STRING ||
last.getType() != Token.STRING) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.GET_REF:
add(first);
break;
case Token.REF_SPECIAL:
Preconditions.checkState(childCount == 1);
add(first);
add(".");
add((String) n.getProp(Node.NAME_PROP));
break;
case Token.FUNCTION:
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Preconditions.checkState(childCount == 3);
boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().getType() == Token.SCRIPT);
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.getType() == Token.VAR) {
cc.endStatement();
}
if (c.getType() == Token.FUNCTION) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top level statements are more homogeneous.
if (preferLineBreaks) {
cc.notePreferredLineBreak();
}
}
if (preserveBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.getType() == Token.VAR) {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
}
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyStatement(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(
childCount == 2,
"Bad GETPROP: expected 2 children, but got %s", childCount);
Preconditions.checkState(
last.getType() == Token.STRING,
"Bad GETPROP: RHS should be STRING");
boolean needsParens = (first.getType() == Token.NUMBER);
if (needsParens) {
add("(");
}
addLeftExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(
childCount == 2,
"Bad GETELEM: expected 2 children but got %s", childCount);
addLeftExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add("with(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.INC:
case Token.DEC: {
Preconditions.checkState(childCount == 1);
String o = type == Token.INC ? "++" : "--";
int postProp = n.getIntProp(Node.INCRDECR_PROP);
// A non-zero post-prop value indicates a post inc/dec, default of zero
// is a pre-inc/dec.
if (postProp != 0) {
addLeftExpr(first, NodeUtil.precedence(type), context);
cc.addOp(o, false);
} else {
cc.addOp(o, false);
add(first);
}
break;
}
case Token.CALL:
// We have two special cases here:
// 1) If the left hand side of the call is a direct reference to eval,
// then it must have a DIRECT_EVAL annotation. If it does not, then
// that means it was originally an indirect call to eval, and that
// indirectness must be preserved.
// 2) If the left hand side of the call is a property reference,
// then the call must not a FREE_CALL annotation. If it does, then
// that means it was originally an call without an explicit this and
// that must be preserved.
if (isIndirectEval(first)
|| n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) {
add("(0,");
addExpr(first, NodeUtil.precedence(Token.COMMA));
add(")");
} else {
addLeftExpr(first, NodeUtil.precedence(type), context);
}
add("(");
addList(first.getNext());
add(")");
break;
case Token.IF:
boolean hasElse = childCount == 3;
boolean ambiguousElseClause =
context == Context.BEFORE_DANGLING_ELSE && !hasElse;
if (ambiguousElseClause) {
cc.beginBlock();
}
add("if(");
add(first);
add(")");
if (hasElse) {
addNonEmptyStatement(
first.getNext(), Context.BEFORE_DANGLING_ELSE, false);
add("else");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
addNonEmptyStatement(first.getNext(), Context.OTHER, false);
Preconditions.checkState(childCount == 2);
}
if (ambiguousElseClause) {
cc.endBlock();
}
break;
case Token.NULL:
case Token.THIS:
case Token.FALSE:
case Token.TRUE:
Preconditions.checkState(childCount == 0);
add(Node.tokenToName(type));
break;
case Token.CONTINUE:
Preconditions.checkState(childCount <= 1);
add("continue");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>(" ");
add(first);
}
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc.endStatement();
break;
case Token.BREAK:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_VOID:
throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
case Token.EXPR_RESULT:
Preconditions.checkState(childCount == 1);
add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parentheses. Otherwise, when parsed, NEW will bind to the
// first viable parentheses (don't traverse into functions).
if (NodeUtil.containsType(first, Token.CALL, new MatchNotFunction())) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING:
Preconditions.checkState(
childCount ==
((n.getParent() != null &&
n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0),
n.getParent().toStringTree());
add(jsString(n.getString(), outputCharsetEncoder));
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext()) {
if (c != first) {
cc.listSeparator();
}
// Object literal property names don't have to be quoted if they are
// not JavaScript keywords
if (c.getType() == Token.STRING &&
!TokenStream.isKeyword(c.getString()) &&
TokenStream.isJSIdentifier(c.getString()) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(c.getString())) {
add(c.getString());
} else {
addExpr(c, 1);
}
add(":");
addExpr(c.getFirstChild(), 1);
}
add("}");
if (needsParens) {
add(")");
}
break;
}
case Token.SWITCH:
add("switch(");
add(first);
add(")");
cc.beginBlock();
addAllSiblings(first.getNext());
cc.end
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>.getType() == Token.EMPTY) {
cc.endStatement(true);
} else {
add(nodeToProcess, context);
// VAR doesn't include ';' since it gets used in expressions - so any
// VAR in a statement context needs a call to endStatement() here.
if (nodeToProcess.getType() == Token.VAR) {
cc.endStatement();
}
}
}
/**
* @return Whether the Node is a DO or FUNCTION (with or without
* labels).
*/
private boolean isOneExactlyFunctionOrDo(Node n) {
if (n.getType() == Token.LABEL) {
Node labeledStatement = n.getLastChild();
if (labeledStatement.getType() != Token.BLOCK) {
return isOneExactlyFunctionOrDo(labeledStatement);
} else {
// For labels with block children, we need to ensure that a
// labeled FUNCTION or DO isn't generated when extraneous BLOCKs
// are skipped.
if (getNonEmptyChildCount(n, 2) == 1) {
return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n));
} else {
// Either a empty statement or an block with more than one child,
// way it isn't a FUNCTION or DO.
return false;
}
}
} else {
return (n.getType() == Token.FUNCTION || n.getType() == Token.DO);
}
}
/**
* Adds a node at the left-hand side of an expression. Unlike
* {@link #addExpr(Node,int)}, this preserves information about the context.
*
* The left side of an expression is special because in the JavaScript
* grammar, certain tokens may be parsed differently when they are at
* the beginning of a statement. For example, "{}" is parsed as a block,
* but "{'x': 'y'}" is parsed as an object literal.
*/
void addLeftExpr(Node n, int minPrecedence, Context context) {
addExpr(n, minPrecedence, context);
}
void addExpr(Node n, int minPrecedence) {
addExpr(n, minPrecedence, Context.OTHER);
}
private void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) &&
(n.getType() == Token.IN))){
add("(");
add(n, clearContextForNoInOperator(context));
add(")");
} else {
add(n, context);
}
}
void addList(Node firstInList) {
addList(firstInList, true, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument) {
addList(firstInList, isArrayOrFunctionArgument, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument,
Context lhsContext) {
for (Node n = firstInList; n != null; n = n.getNext()) {
boolean isFirst = n == firstInList;
if (isFirst) {
addLeftExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext);
} else {
cc.listSeparator
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
String singlequoteEscape,
String backslashEscape,
CharsetEncoder outputCharsetEncoder) {
StringBuilder sb = new StringBuilder(s.length() + 2);
sb.append(quote);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\\': sb.append(backslashEscape); break;
case '\"': sb.append(doublequoteEscape); break;
case '\'': sb.append(singlequoteEscape); break;
case '>': // Break --> into --\> or ]]> into ]]\>
if (i >= 2 &&
((s.charAt(i - 1) == '-' && s.charAt(i - 2) == '-') ||
(s.charAt(i - 1) == ']' && s.charAt(i - 2) == ']'))) {
sb.append("\\>");
} else {
sb.append(c);
}
break;
case '<': // Break </script into <\/script
final String END_SCRIPT = "/script";
if (s.regionMatches(true, i + 1, END_SCRIPT, 0,
END_SCRIPT.length())) {
sb.append("<\\");
} else {
sb.append(c);
}
break;
default:
// If we're given an outputCharsetEncoder, then check if the
// character can be represented in this character set.
if (outputCharsetEncoder != null) {
if (outputCharsetEncoder.canEncode(c)) {
sb.append(c);
} else {
// Unicode-escape the character.
appendHexJavaScriptRepresentation(sb, c);
}
} else {
// No charsetEncoder provided - pass straight latin characters
// through, and escape the rest. Doing the explicit character
// check is measurably faster than using the CharsetEncoder.
if (c > 0x1f && c <= 0x7f) {
sb.append(c);
} else {
// Other characters can be misinterpreted by some js parsers,
// or perhaps mangled by proxies along the way,
// so we play it safe and unicode escape them.
appendHexJavaScriptRepresentation(sb, c);
}
}
}
}
sb.append(quote);
return sb.toString();
}
static String identifierEscape(String s) {
// First check if escaping is needed at all -- in most cases it isn't.
if (NodeUtil.isLatin(s)) {
return s;
}
// Now going through the string to escape non-latin characters if needed.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// Identifiers should always go to Latin1/ ASCII characters because
// different browser's rules for valid identifier characters are
// crazy.
if (c > 0x1F && c < 0x7F) {
sb.append(c);
} else {
appendHexJavaScriptRepresentation(sb, c);
}
}
return
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
{
return version == VERSION_DEFAULT || version >= VERSION_1_3;
}
static String getSourcePositionFromStack(int[] linep)
{
Context cx = getCurrentContext();
if (cx == null)
return null;
/**
* A bit of a hack, but the only way to get filename and line
* number from an enclosing frame.
*/
CharArrayWriter writer = new CharArrayWriter();
RuntimeException re = new RuntimeException();
re.printStackTrace(new PrintWriter(writer));
String s = writer.toString();
int open = -1;
int close = -1;
int colon = -1;
for (int i=0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == ':')
colon = i;
else if (c == '(')
open = i;
else if (c == ')')
close = i;
else if (c == '\n' && open != -1 && close != -1 && colon != -1 &&
open < colon && colon < close)
{
String fileStr = s.substring(open + 1, colon);
if (!fileStr.endsWith(".java")) {
String lineStr = s.substring(colon + 1, close);
try {
linep[0] = Integer.parseInt(lineStr);
if (linep[0] < 0) {
linep[0] = 0;
}
return fileStr;
}
catch (NumberFormatException e) {
// fall through
}
}
open = close = colon = -1;
}
}
return null;
}
public final boolean isGeneratingDebugChanged()
{
return generatingDebugChanged;
}
/**
* Add a name to the list of names forcing the creation of real
* activation objects for functions.
*
* @param name the name of the object to add to the list
*/
public void addActivationName(String name)
{
if (sealed) onSealedMutation();
if (activationNames == null)
activationNames = new Hashtable<Object, Object>(5);
activationNames.put(name, name);
}
/**
* Check whether the name is in the list of names of objects
* forcing the creation of activation objects.
*
* @param name the name of the object to test
*
* @return true if an function activation object is needed.
*/
public final boolean isActivationNeeded(String name)
{
return activationNames != null && activationNames.containsKey(name);
}
/**
* Remove a name from the list of names forcing the creation of real
* activation objects for functions.
*
* @param name the name of the object to remove from the list
*/
public void removeActivationName(String name)
{
if (sealed) onSealedMutation();
if (activationNames != null)
activationNames.remove(name);
}
private static String implementationVersion;
private boolean sealed;
private Object sealKey;
// for Objects, Arrays to tag themselves as being printed out,
// so they don't print themselves out recursively.
// Use ObjToIntMap instead of java.util.HashSet for JDK 1.1 compatibility
ObjToIntMap iterating;
Object interpreterSecurityDomain;
int version;
private ErrorReporter errorReporter
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.declaration;
if (!isDefined) {
if (declaration != null) {
reportRefToUndefinedName(name, declaration);
}
}
if (name.refs != null) {
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.refs) {
if (!isDefined) {
reportRefToUndefinedName(name, ref);
} else {
if (declaration != null &&
ref.module != declaration.module &&
!moduleGraph.dependsOn(ref.module, declaration.module)) {
reportBadModuleReference(name, ref);
}
}
}
}
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME,
ref.module.getName(), name.declaration.module.getName(),
name.fullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.getType() == Token.EXPR_RESULT) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return;
}
}
compiler.report(
JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING,
name.fullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private static boolean propertyMustBeInitializedByFullName(Name name) {
// If an object literal in the global namespace is never aliased,
// then all of its properties must be defined using its full qualified
// name. This implies that its properties must all be in the global
// namespace as well.
//
// The same is not true for FUNCTION and OTHER types, because their
// implicit prototypes have properties that are not captured by the global
// namespace.
return name.parent != null && name.parent.aliasingGets == 0 &&
name.parent.type == Name.Type.OBJECTLIT;
}
}
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.getType() == Token.GETPROP) {
String qualifiedName = callee.getQualifiedName();
if ((functionName).equals(qualifiedName)) {
className = callee.getNext().getString();
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.getType() == Token.ARRAYLIT) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.getType() == Token.STRING) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return null;
}
@Override
public String identifyTypeDefAssign(Node n) {
Node firstChild = n.getFirstChild();
int type = n.getType();
if (type == Token.ASSIGN) {
if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) {
return firstChild.getQualifiedName();
}
} else if (type == Token.VAR && firstChild.hasChildren()) {
if (TYPEDEF_NAME.equals(
firstChild.getFirstChild().getQualifiedName())) {
return firstChild.getString();
}
}
return null;
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.getQualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.addSingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return null;
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
functionType.defineDeclaredProperty("getInstance", getterType, false);
functionType.defineDeclaredProperty("instance_", objectType, false);
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> return lineno << COLUMN_BITS | (charno & COLUMN_MASK);
}
}
/**
* Extracts the line number and character number from a merged line char
* number (see {@link #mergeLineCharNo(int, int)}).
*/
protected static int extractLineno(int lineCharNo) {
if (lineCharNo == -1) {
return -1;
} else {
return lineCharNo >>> COLUMN_BITS;
}
}
/**
* Extracts the character number and character number from a merged line
* char number (see {@link #mergeLineCharNo(int, int)}).
*/
protected static int extractCharno(int lineCharNo) {
if (lineCharNo == -1) {
return -1;
} else {
return lineCharNo & COLUMN_MASK;
}
}
//==========================================================================
// Iteration
/**
* <p>Return an iterable object that iterates over this nodes's children.
* The iterator does not support the optional operation
* {@link Iterator#remove()}.</p>
*
* <p>To iterate over a node's siblings, one can write</p>
* <pre>Node n = ...;
* for (Node child : n.children()) { ...</pre>
*/
public Iterable<Node> children() {
if (first == null) {
return Collections.emptySet();
} else {
return new SiblingNodeIterable(first);
}
}
/**
* <p>Return an iterable object that iterates over this nodes's siblings.
* The iterator does not support the optional operation
* {@link Iterator#remove()}.</p>
*
* <p>To iterate over a node's siblings, one can write</p>
* <pre>Node n = ...;
* for (Node sibling : n.siblings()) { ...</pre>
*/
public Iterable<Node> siblings() {
return new SiblingNodeIterable(this);
}
/**
* @see Node#siblings()
*/
private static final class SiblingNodeIterable
implements Iterable<Node>, Iterator<Node> {
private final Node start;
private Node current;
private boolean used;
SiblingNodeIterable(Node start) {
this.start = start;
this.current = start;
this.used = false;
}
public Iterator<Node> iterator() {
if (!used) {
used = true;
return this;
} else {
// We have already used the current object as an iterator;
// we must create a new SiblingNodeIterable based on this
// iterable's start node.
//
// Since the primary use case for Node.children is in for
// loops, this branch is extremely unlikely.
return (new SiblingNodeIterable(start)).iterator();
}
}
public boolean hasNext() {
return current != null;
}
public Node next() {
if (current == null) {
throw new NoSuchElementException();
}
try {
return current;
} finally {
current = current.getNext();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// ==========================================================================
// Accessors
public Node getParent() {
return parent;
}
/**
* Gets the ancestor node relative to this.
*
* @param level
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while (node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
public Iterator<Node> iterator() {
return new Iterator<Node>() {
public boolean hasNext() {
return cur != null;
}
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
Node n = cur;
cur = cur.getParent();
return n;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next)
c++;
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
if (diff != null) {
return "Node tree inequality:" +
"\nTree1:\n" + toStringTree() +
"\n\nTree2:\n" + node2.toStringTree() +
"\n\nSubtree1: " + diff.nodeA.toStringTree() +
"\n\nSubtree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* If this is a compilation pass and not a test, do not construct error
* strings. Instead return true if the trees are equal.
*/
public boolean checkTreeEqualsSilent(Node node2) {
return checkTreeEqualsImpl(node2) == null;
}
/**
* Helper function to ignore differences in Node subclasses that are no longer
* used.
*/
@
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>(String fileLevelComment) {
JSDocInfo jsDocInfo = getJSDocInfo();
if (jsDocInfo == null) {
// TODO(user): Is there a way to determine whether to
// parse the JsDoc documentation from here?
jsDocInfo = new JSDocInfo(false);
}
String license = jsDocInfo.getLicense();
if (license == null) {
license = "";
}
jsDocInfo.setLicense(license + fileLevelComment);
setJSDocInfo(jsDocInfo);
}
}
/**
* Get the {@link JSDocInfo} attached to this node.
* @return the information or {@code null} if no JSDoc is attached to this
* node
*/
public JSDocInfo getJSDocInfo() {
return (JSDocInfo) getProp(JSDOC_INFO_PROP);
}
/**
* Sets the {@link JSDocInfo} attached to this node.
*/
public void setJSDocInfo(JSDocInfo info) {
putProp(JSDOC_INFO_PROP, info);
}
/**
* Sets whether this node is a variable length argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setVarArgs(boolean varArgs) {
putBooleanProp(VAR_ARGS_NAME, varArgs);
}
/**
* Returns whether this node is a variable length argument node. This
* method's return value is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public boolean isVarArgs() {
return getBooleanProp(VAR_ARGS_NAME);
}
/**
* Sets whether this node is an optional argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setOptionalArg(boolean optionalArg) {
putBooleanProp(OPT_ARG_NAME, optionalArg);
}
/**
* Returns whether this node is an optional argument node. This
* method's return value is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public boolean isOptionalArg() {
return getBooleanProp(OPT_ARG_NAME);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setIsSyntheticBlock(boolean val) {
putBooleanProp(SYNTHETIC_BLOCK_PROP, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean isSyntheticBlock() {
return getBooleanProp(SYNTHETIC_BLOCK_PROP);
}
/**
* Sets the ES5 directives on this node.
*/
public void setDirectives(Set<String> val) {
putProp(DIRECTIVES, val);
}
/**
* Returns the set of ES5 directives for this node.
*/
@SuppressWarnings("unchecked")
public Set<String> getDirectives() {
return (Set<String>) getProp(DIRECTIVES);
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> }
/**
* Adds a warning to be suppressed. This is indistinguishable
* from having a {@code @suppress} tag in the code.
*/
public void addSuppression(String warning) {
if (getJSDocInfo() == null) {
setJSDocInfo(new JSDocInfo(false));
}
getJSDocInfo().addSuppression(warning);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setWasEmptyNode(boolean val) {
putBooleanProp(EMPTY_BLOCK, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean wasEmptyNode() {
return getBooleanProp(EMPTY_BLOCK);
}
// There are four values of interest:
// global state changes
// this state changes
// arguments state changes
// whether the call throws an exception
// locality of the result
// We want a value of 0 to mean "global state changes and
// unknown locality of result".
final public static int FLAG_GLOBAL_STATE_UNMODIFIED = 1;
final public static int FLAG_THIS_UNMODIFIED = 2;
final public static int FLAG_ARGUMENTS_UNMODIFIED = 4;
final public static int FLAG_NO_THROWS = 8;
final public static int FLAG_LOCAL_RESULTS = 16;
final public static int SIDE_EFFECTS_FLAGS_MASK = 31;
final public static int SIDE_EFFECTS_ALL = 0;
final public static int NO_SIDE_EFFECTS =
FLAG_GLOBAL_STATE_UNMODIFIED
| FLAG_THIS_UNMODIFIED
| FLAG_ARGUMENTS_UNMODIFIED
| FLAG_NO_THROWS;
/**
* Marks this function or constructor call's side effect flags.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setSideEffectFlags(int flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
}
public SideEffectFlags(int value) {
this.value = value;
}
public int valueOf() {
return value;
}
/** All side-effect occur and the returned results are non-local. */
public void setAllFlags() {
value = Node.SIDE_EFFECTS_ALL;
}
/** No
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import java.util.Set;
/**
* A builder for {@link JSDocInfo} objects. This builder abstracts the
* construction process of {@link JSDocInfo} objects whilst minimizing the
* number of instances of {@link JSDocInfo} objects. It provides early
* incompatibility detection among properties stored on the {@code JSDocInfo}
* object being created.
*
*/
final public class JSDocInfoBuilder {
// the current JSDoc which is being populated
private JSDocInfo currentInfo;
// whether the current JSDocInfo has valuable information
private boolean populated = false;
// whether to include the documentation itself when parsing the JsDoc
private boolean parseDocumentation = false;
// the current marker, if any.
private JSDocInfo.Marker currentMarker = null;
public JSDocInfoBuilder(boolean parseDocumentation) {
this.currentInfo = new JSDocInfo(parseDocumentation);
this.parseDocumentation = parseDocumentation;
}
public boolean shouldParseDocumentation() {
return parseDocumentation;
}
/**
* Returns whether this builder is populated with information that can be
* used to {@link #build} a {@link JSDocInfo} object.
*/
public boolean isPopulated() {
return populated;
}
/**
* Returns whether this builder is populated with information that can be
* used to {@
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>link #build} a {@link JSDocInfo} object that has a
* fileoverview tag.
*/
public boolean isPopulatedWithFileOverview() {
return isPopulated() &&
(currentInfo.hasFileOverview() || currentInfo.isExterns() ||
currentInfo.isNoCompile());
}
/**
* Returns whether this builder recorded a description.
*/
public boolean isDescriptionRecorded() {
return currentInfo.getDescription() != null;
}
/**
* Builds a {@link JSDocInfo} object based on the populated information and
* returns it. Once this method is called, the builder can be reused to build
* another {@link JSDocInfo} object.
*
* @param sourceName The source file containing the JSDoc.
* @return a {@link JSDocInfo} object populated with the values given to this
* builder. If no value was populated, this method simply returns
* {@code null}
*/
public JSDocInfo build(String sourceName) {
if (populated) {
JSDocInfo built = currentInfo;
built.setSourceName(sourceName);
populateDefaults(built);
populated = false;
currentInfo = new JSDocInfo(this.parseDocumentation);
return built;
} else {
return null;
}
}
/** Generate defaults when certain parameters are not specified. */
private static void populateDefaults(JSDocInfo info) {
if (info.getVisibility() == null) {
info.setVisibility(Visibility.INHERITED);
}
}
/**
* Adds a marker to the current JSDocInfo and populates the marker with the
* annotation information.
*/
public void markAnnotation(String annotation, int lineno, int charno) {
JSDocInfo.Marker marker = currentInfo.addMarker();
if (marker != null) {
marker.annotation = new JSDocInfo.StringPosition();
marker.annotation.setItem(annotation);
marker.annotation.setPositionInformation(lineno, charno, lineno,
charno + annotation.length());
}
currentMarker = marker;
}
/**
* Adds a textual block to the current marker.
*/
public void markText(String text, int startLineno, int startCharno, int endLineno,
int endCharno) {
if (currentMarker != null) {
currentMarker.description = new JSDocInfo.StringPosition();
currentMarker.description.setItem(text);
currentMarker.description.setPositionInformation(startLineno, startCharno,
endLineno, endCharno);
}
}
/**
* Adds a type declaration to the current marker.
*/
public void markTypeNode(Node typeNode, int lineno, int startCharno, int endCharno,
boolean hasLC) {
if (currentMarker != null) {
currentMarker.type = new JSDocInfo.TypePosition();
currentMarker.type.setItem(typeNode);
currentMarker.type.hasBrackets = hasLC;
currentMarker.type.setPositionInformation(lineno, startCharno,
lineno, endCharno);
}
}
/**
* Adds a name declaration to the current marker.
*/
public void markName(String name, int lineno, int charno) {
if (
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>currentMarker != null) {
currentMarker.name = new JSDocInfo.StringPosition();
currentMarker.name.setItem(name);
currentMarker.name.setPositionInformation(lineno, charno,
lineno, charno + name.length());
}
}
/**
* Records a block-level description.
*
* @return {@code true} if the description was recorded.
*/
public boolean recordBlockDescription(String description) {
populated = true;
return currentInfo.documentBlock(description);
}
/**
* Records a visibility.
*
* @return {@code true} if the visibility was recorded and {@code false}
* if it was already defined
*/
public boolean recordVisibility(Visibility visibility) {
if (currentInfo.getVisibility() == null) {
populated = true;
currentInfo.setVisibility(visibility);
return true;
} else {
return false;
}
}
/**
* Records a typed parameter.
*
* @return {@code true} if the typed parameter was recorded and
* {@code false} if a parameter with the same name was already defined
*/
public boolean recordParameter(String parameterName, JSTypeExpression type) {
if (!hasAnySingletonTypeTags() &&
currentInfo.declareParam(type, parameterName)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records a parameter's description.
*
* @return {@code true} if the parameter's description was recorded and
* {@code false} if a parameter with the same name was already defined
*/
public boolean recordParameterDescription(String parameterName, String description) {
if (currentInfo.documentParam(parameterName, description)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records a template type name.
*
* @return {@code true} if the template type name was recorded and
* {@code false} if a template type name was already defined.
*/
public boolean recordTemplateTypeName(String name) {
if (currentInfo.declareTemplateTypeName(name)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records a thrown type.
*/
public boolean recordThrowType(JSTypeExpression type) {
if (!hasAnySingletonTypeTags()) {
currentInfo.declareThrows(type);
populated = true;
return true;
}
return false;
}
/**
* Records a throw type's description.
*
* @return {@code true} if the type's description was recorded and
* {@code false} if a description with the same type was already defined
*/
public boolean recordThrowDescription(JSTypeExpression type, String description) {
if (currentInfo.documentThrows(type, description)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Adds an author to the current information.
*/
public boolean addAuthor(String author) {
if (currentInfo.documentAuthor(author)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Adds a reference ("@see") to the current information.
*/
public boolean addReference(String reference
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>
currentInfo.setNoShadow(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isImplicitCast()} flag set to {@code true}.
*/
public boolean recordImplicitCast() {
if (!currentInfo.isImplicitCast()) {
currentInfo.setImplicitCast(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isNoSideEffects()} flag set to {@code true}.
*/
public boolean recordNoSideEffects() {
if (!hasAnySingletonSideEffectTags()
&& !currentInfo.isNoSideEffects()) {
currentInfo.setNoSideEffects(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isExterns()} flag set to {@code true}.
*/
public boolean recordExterns() {
if (!currentInfo.isExterns()) {
currentInfo.setExterns(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Whether the {@link JSDocInfo} being built will have its
* {@link JSDocInfo#isInterface()} flag set to {@code true}.
*/
public boolean isInterfaceRecorded() {
return currentInfo.isInterface();
}
/**
* @return Whether a parameter of the given name has already been recorded.
*/
public boolean hasParameter(String name) {
return currentInfo.hasParameter(name);
}
/**
* Records an implemented interface.
*/
public boolean recordImplementedInterface(JSTypeExpression interfaceName) {
if (currentInfo.addImplementedInterface(interfaceName)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that we're lending to another name.
*/
public boolean recordLends(String name) {
if (!hasAnyTypeRelatedTags()) {
currentInfo.setLendsName(name);
populated = true;
return true;
} else {
return false;
}
}
/**
* Whether the current doc info has other type tags, like
* {@code @param} or {@code @return} or {@code @type} or etc.
*/
private boolean hasAnyTypeRelatedTags() {
return currentInfo.isConstructor() ||
currentInfo.isInterface() ||
currentInfo.getParameterCount() > 0 ||
currentInfo.hasReturnType() ||
currentInfo.hasBaseType() ||
currentInfo.getLendsName() != null ||
currentInfo.hasThisType() ||
hasAnySingletonTypeTags();
}
/**
* Whether the current doc info has any of the singleton type
* tags that may not appear with other type tags, like
* {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonTypeTags() {
return currentInfo.hasType() ||
currentInfo.hasType
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS> NOT_A_CLASS =
"Not declared as a constructor";
public static final String NOT_A_TYPE =
"Not declared as a type name";
public static final String EMPTY_TYPE_COMPONENT =
"Named type with empty name component";
/**
* Total ordering on types based on their textual representation.
* This is used to have a deterministic output of the toString
* method of the union type since this output is used in tests.
*/
static final Comparator<JSType> ALPHA = new Comparator<JSType>() {
public int compare(JSType t1, JSType t2) {
return t1.toString().compareTo(t2.toString());
}
};
// A flag set on enum definition tree nodes
public static final int ENUMDECL = 1;
public static final int NOT_ENUMDECL = 0;
final JSTypeRegistry registry;
JSType(JSTypeRegistry registry) {
this.registry = registry;
}
/**
* Utility method for less verbose code.
*/
JSType getNativeType(JSTypeNative typeId) {
return registry.getNativeType(typeId);
}
/**
* Gets the docInfo for this type. By default, documentation cannot be
* attached to arbitrary types. This must be overridden for
* programmer-defined types.
*/
public JSDocInfo getJSDocInfo() {
return null;
}
/**
* If we see a type name without braces, it might be legacy jsdoc.
* So we shouldn't emit warnings about it. This method is how we skip
* those warnings.
*/
void forgiveUnknownNames() {}
public boolean isNoType() {
return false;
}
public boolean isNoObjectType() {
return false;
}
public final boolean isEmptyType() {
return isNoType() || isNoObjectType();
}
public boolean isNumberObjectType() {
return false;
}
public boolean isNumberValueType() {
return false;
}
/** Whether this is the prototype of a function. */
public boolean isFunctionPrototypeType() {
return false;
}
public boolean isStringObjectType() {
return false;
}
boolean isTheObjectType() {
return false;
}
public boolean isStringValueType() {
return false;
}
/**
* Tests whether the type is a string (value or Object).
* @return {@code this <: (String, string)}
*/
public final boolean isString() {
return this.isSubtype(
getNativeType(JSTypeNative.STRING_VALUE_OR_OBJECT_TYPE));
}
/**
* Tests whether the type is a number (value or Object).
* @return {@code this <: (Number, number)}
*/
public final boolean isNumber() {
return this.isSubtype(
getNativeType(JSTypeNative.NUMBER_VALUE_OR_OBJECT_TYPE));
}
public boolean isArrayType() {
return false;
}
public boolean isBooleanObjectType() {
return false;
}
public boolean isBooleanValueType() {
return false;
}
public boolean isRegexpType() {
return false;
}
public boolean isDateType() {
return false;
}
public boolean isNullType() {
return false;
}
public boolean isVoidType() {
return false;
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* <p>JSDoc information describing JavaScript code. JSDoc is represented as a
* unified object with fields for each JSDoc annotation, even though some
* combinations are incorrect. For instance, if a JSDoc describes an enum,
* it cannot have information about a return type. This implementation
* takes advantage of such incompatibilities to reuse fields for multiple
* purposes, reducing memory consumption.</p>
*
* <p>Constructing {@link JSDocInfo} objects is simplified by
* {@link JSDocInfoBuilder} which provides early incompatibility detection.</p>
*
*/
public final class JSDocInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Visibility categories. The {@link Visibility#ordinal()} can be used as a
* numerical indicator of privacy, where 0 is the most private. This means
* that the {@link Visibility#compareTo} method can be used to
* determine if a visibility is more permissive than
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>10...
private static final int TYPEFIELD_TYPE = 0x20000000; // 0010...
private static final int TYPEFIELD_RETURN = 0x40000000; // 0100...
private static final int TYPEFIELD_ENUM = 0x60000000; // 0110...
private static final int TYPEFIELD_TYPEDEF = 0x80000000; // 1000...
/**
* Creates a {@link JSDocInfo} object. This object should be created using
* a {@link JSDocInfoBuilder}.
*/
JSDocInfo(boolean includeDocumentation) {
this.includeDocumentation = includeDocumentation;
}
// Visible for testing.
public JSDocInfo() {}
void setConstant(boolean value) {
setFlag(value, MASK_CONSTANT);
}
void setConstructor(boolean value) {
setFlag(value, MASK_CONSTRUCTOR);
}
void setDefine(boolean value) {
setFlag(value, MASK_DEFINE);
}
void setHidden(boolean value) {
setFlag(value, MASK_HIDDEN);
}
void setNoCheck(boolean value) {
setFlag(value, MASK_NOCHECK);
}
void setShouldPreserveTry(boolean value) {
setFlag(value, MASK_PRESERVETRY);
}
void setOverride(boolean value) {
setFlag(value, MASK_OVERRIDE);
}
void setNoAlias(boolean value) {
setFlag(value, MASK_NOALIAS);
}
// Visible for testing.
public void setDeprecated(boolean value) {
setFlag(value, MASK_DEPRECATED);
}
void setInterface(boolean value) {
setFlag(value, MASK_INTERFACE);
}
void setExport(boolean value) {
setFlag(value, MASK_EXPORT);
}
void setNoShadow(boolean value) {
setFlag(value, MASK_NOSHADOW);
}
void setImplicitCast(boolean value) {
setFlag(value, MASK_IMPLICITCAST);
}
void setNoSideEffects(boolean value) {
setFlag(value, MASK_NOSIDEEFFECTS);
}
void setExterns(boolean value) {
setFlag(value, MASK_EXTERNS);
}
void setJavaDispatch(boolean value) {
setFlag(value, MASK_JAVADISPATCH);
}
void setNoCompile(boolean value) {
setFlag(value, MASK_NOCOMPILE);
}
private void setFlag(boolean value, int mask) {
if (value) {
bitset |= mask;
} else {
bitset &= ~mask;
}
}
/**
* Returns whether the {@code @const} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isConstant() {
return getFlag(MASK_CONSTANT) || isDefine();
}
/**
* Returns whether the {@code @constructor} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isConstructor() {
return getFlag(MASK_CONSTRUCTOR);
}
/**
* Returns whether the {@code @define} annotation
Closure, 91
<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING ||
pType == Token.NUMBER)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
<CHANGES>
<CHANGEE>
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
<FILEE>
<SCANS>.
*/
public JSTypeExpression getThisType() {
return thisType;
}
/**
* Sets the type specified by the {@code @this} annotation.
*/
void setThisType(JSTypeExpression type) {
this.thisType = type;
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @this}
* annotation.
*/
public boolean hasThisType() {
return thisType != null;
}
void setBaseType(JSTypeExpression type) {
lazyInitInfo();
info.baseType = type;
}
/**
* Gets the base type specified by the {@code @extends} annotation.
*/
public JSTypeExpression getBaseType() {
return (info == null) ? null : info.baseType;
}
/**
* Gets the description specified by the {@code @desc} annotation.
*/
public String getDescription() {
return (info == null) ? null : info.description;
}
void setDescription(String desc) {
lazyInitInfo();
info.description = desc;
}
/**
* Gets the meaning specified by the {@code @meaning} annotation.
*
* In localization systems, two messages with the same content but
* different "meanings" may be translated differently. By default, we
* use the name of the variable that the message is initialized to as
* the "meaning" of the message.
*
* But some code generators (like Closure Templates) inject their own
* meaning with the jsdoc {@code @meaning} annotation.
*/
public String getMeaning() {
return (info == null) ? null : info.meaning;
}
void setMeaning(String meaning) {
lazyInitInfo();
info.meaning = meaning;
}
/**
* Gets the name we're lending to in a {@code @lends} annotation.
*
* In many reflection APIs, you pass an anonymous object to a function,
* and that function mixes the anonymous object into another object.
* The {@code @lends} annotation allows the type system to track
* those property assignments.
*/
public String getLendsName() {
return (info == null) ? null : info.lendsName;
}
void setLendsName(String name) {
lazyInitInfo();
info.lendsName = name;
}
/**
* Gets the description specified by the {@code @license} annotation.
*/
public String getLicense() {
return (info == null) ? null : info.license;
}
/** License directives can appear in multiple comments, and always
* apply to the entire file. Break protection and allow outsiders to
* update the license string so that we can attach the license text even
* when the JSDocInfo has been created and tagged with other information.
* @param license String containing new license text.
*/
public void setLicense(String license) {
lazyInitInfo();
info.license = license;
}
@Override
public String toString() {
return "JSDocInfo";
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @extends}
* annotation.
*/
public boolean hasBaseType() {
return getBaseType() != null;
}
/**
*